一小子攻城狮
想与博主以及众群友“发生关系”的请点击加【群243143941】;
  • 攻城狮
  • 身处网络
  • 随笔
9月22015

mysql长连接

作者:老温   发布:2015-9-2 17:14 Wednesday   分类:攻城狮   阅读:148次   评论:0条  

PHP的MySQL持久化连接,美好的目标,却拥有糟糕的口碑,往往令人敬而远之。这到底是为啥么。近距离观察后发现,这家伙也不容易啊,要看Apache的脸色,还得听MySQL指挥。

对于作为Apache模块运行的PHP来说,要实现MySQL持久化连接,首先得取决于Apache这个web服务器是否支持Keep-Alive。

Keep-Alive
Keep-Alive是什么东西?它是http协议的一部分,让我们复习一下没有Keep-Alive的http请求,从客户在浏览器输入一个有效url地址开始,浏览器就会利用socket向url对应的web服务器发送一条TCP请求,这个请求成功一次就得需要来回握三次手才能确定,成功以后,浏览器利用socket TCP连接资源向web服务器请求http协议,发送以后就等着web服务器把http返回头和body发送回来,发回来后浏览器关闭socket连接,然后做http返回头和body的解析工作,最后呈现在浏览器上的就是漂亮的页面了。这里面有什么问题呢?TCP连接需要三次握手,也就是来回请求三次方能确定一个TCP请求是否成功,然后TCP关闭呢?来回需要4次请求才能完成!每次http请求就3次握手,4次拜拜,这来来回回的不嫌累啊,多少时间和资源都被浪费在socket连接关闭上了,能不能一次socket TCP连接发送多次http请求呢?于是Keep-Alive就应运而生,http/1.0里需要客户端自己在请求头加入Connection:Keep-alive方能实现,在这里我们只考虑http1.1了,只需要设置一下Apache,让它默认就是Keep-Alive持久连接模式(Apache必须1.2+才能支持Keep-Alive)。在httpd.conf里找到KeepAive配置项,果断设置为On,MaxKeepAliveRequests果断为0(一个持久TCP最多允许的请求数,如果过小,很容易在TCP未过期的情况下,达到最大连接,那下次连接就又是新的TCP连接了,这里设置0表示不限制),然后对于mysql_pconnect最重要的选项KeepAliveTimeout设置为15(表示15秒)。

好了,重启Apache,测试一下,赶紧写行东西:


<?php
    echo "Apache进程号:". getmypid();
?>


很简单,获取当前PHP执行者(Apache)的进程号,用浏览器浏览这个页面,看到什么?对,有看到一串进程号数字,15秒内,连续刷新页面,看看进程号有无变化?木有吧?现在把手拿开,交叉在胸前,度好时间,1秒,2秒,3,…15,16。好,过了15秒了,再去刷新页面,进程号有没有变化?变了!又是一个新的Apache进程了,为什么15秒后就变成新的进程了?记得我们在Apache里设置的KeepAliveTimeout吗?它的值就是15秒。现在我们应该大致清楚了,在web服务器默认打开KeepAlive的情况下,客户端第一次http成功请求后,Apache不会立刻断开socket,而是一直监听来自这一客户端的请求,监听多久?根据KeepAliveTimeout选项配置的时间决定,一旦超过这一时间,Apache就会断开socket了,那么下次同一客户端再次请求,Apache就会新开一个进程来相应。所以我们之前15内不停的刷新页面,看到的进程号都是一致的,表明是浏览器请求给了同一个Apache进程。

浏览器是怎么知道不需要重新进行TCP连接就可以直接发送http请求呢?因为http返回头里就会带上Connection:keep-alive,Keep-alive:15两行,意思就是让客户端浏览器明白,这次socket连接我这边还没关闭呢,你可以在15内继续使用这个连接,并发送http请求,于是乎浏览器就知道应该怎么做了。

PHP怎么做
那么,PHP的MySQL连接资源是怎么被hold住的呢,这需要查看PHP的mysql_pconnect的函数代码,我看了下,大概的做法就是mysql_pconnect根据当前Apache进程号,生成hash key,找hash表内有无对应的连接资源,没有则推入hash表,有则直接使用。有些代码片段可以说明(具体可查看PHP5.3.8源码ext/mysql/PHP_mysql.c文件690行PHP_mysql_do_connect函数)


<!-- lang: cpp -->
#1.生成hash key
    user=php_get_current_user();//获取当前PHP执行者(Apache)的进程唯一标识号
    //hashed_details就是hash key
    hashed_details_length = spprintf(&hashed_details, 0, "MySQL__%s_", user);
    #2.如果未找到已有资源,就推入hash表,名字叫persistent_list,如果找到就直接使用
    /* try to find if we already have this link in our persistent list */
    if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)==FAILURE) {  
        /* we don't */
        ...
        ...
        /* hash it up(推入hash表) */
        Z_TYPE(new_le) = le_plink;
        new_le.ptr = mysql;
        if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) {
            ...
            ...      
            }
        }
        else
        {/* The link is in our list of persistent connections(连接已在hash表里)*/
            ...
            ...
            mysql = (PHP_mysql_conn *) le->ptr;//直接使用对应的sql连接资源
            ...
            ...
        }


zend_hash_find比较容易看明白,原型是zend_hash_find(hash表,key名,key长,value);如果找到,value就有值了。

MySQL的wait_timeout和interactive_timeout
说完Keep-Alive,该到MySQL家串串门了,说的是mysql_pconnect,怎么能绕开MySQL的设置。影响mysql_pconnect最重要的两个参数就是wait_timeout和interactive_timeout,它们是什么东西?先撇一边,首先让我们把上面的代码改动一下PHP代码


<?php
    $conn = mysql_pconnect("localhost","root","123456") or die("Can not connect to MySQL");
    echo "MySQL线程号:". MySQL_thread_id($conn). "<br />";
    echo "Apache进程号". getmypid();
?>


以上的代码没啥好解释的,让我们用浏览器浏览这个页面,看到什么?看到两个显眼的数字。一个是MySQL线程号,一个是Apache进程号,好了,15秒后再刷新这个页面,发现这两个id都变了,因为已经是新的Apache进程了,进程id是新的,hash key就变了,PHP只好重新连接MySQL,连接资源推入persistent list。如果15内刷新呢?Apache进程肯定不变,MySQL线程号会变吗?答案得问MySQL了。首先这个MySQL_thread_id是什么东西?shell方式登录MySQL后执行命令'show processlist;',看到了什么?


mysql> show processlist;
+-----+------+-----------+------+--------+-----+------+-----------------+ | Id  | User | Host      | db   | Command| Time| State| Info           |
+-----+------+-----------+------+--------+-----+------+-----------------+ | 348 | root | localhost | NULL | Query  |    0| NULL | show processlist| | 349 | root | localhost | NULL | Sleep  |    2|      | NULL            |
+-----+------+-----------+------+--------+-----+------+-----------------+


发现了很重要的信息,这个processlist列表就是记录了正在跑的线程,忽略Info列为show processlist那行,那行是你当前shell登录MySQL的线程。PHP连接MySQL的线程就是Id为349那行,如果读者自己做测试,应该知道这个Id=349在你的测试环境里是另外一个值,我们把这个值和网页里输出的MySQL_thread_id($conn)做做比较,对!他们是一样的。接下来最重要的是观察Command列和Time列,Command = Sleep,表明什么?表明我们mysql_pconnect连接后就一直在sleep,Time字段就告诉我们,这个线程Sleep了多久,那么Sleep了多久这个线程才能作废呢?那就是wait_timeout或者interactive_timeout要做的工作了,他们默认的值都是8小时,天啊,太久了,所以如果说web服务器关掉KeepAlive支持,那个这个processlist很容易就被撑爆,就爆出那个Too many connections的错误了,max_connectiosns配置得再多也没用。为了观察这两个参数,我们可以在MySQL配置文件my.cnf里设置这两个值,找到[MySQLd]节点,在里面设置多两行

interactive_timeout = 60
wait_timeout = 30
配置完后,重启MySQL,shell登录MySQL,这时候show processlist可以发现只有当前线程。然后运行那个带有mysql_pconnect的PHP页面,再回来MySQL端show processlist可发现,多了一个Commond为Sleep的线程,不停的show processlist(方向键上+enter键)观察Time列的变化2,5,10…14!,突然那个Sleep线程程被kill掉了,咋回事,还没到30秒呢,噢!忘了修改一下Apache keepalive的参数了,把KeepAliveTimeOut从15改成120(只为观察,才这么改),重启Apache。刷新那个页面,好,开始不停的show processlist,2..5..10..14,15,..20…26….28,29!线程被kill,这次是因为wait_timeout起了作用,浏览器那边停了30秒,30内如果浏览器刷新,那这个Time又会从0开始计时。这种连接不属于interactive connection(MySQL shell登录那种连接就属于interactive connection),所以采用了wait_timeout的值。如果mysql_pconnect的第4个参数改改呢


<?php
$conn = mysql_pconnect('localhost','root','123456',MySQL_CLIENT_INTERACTIVE);
echo "MySQL线程号:".MySQL_thread_id($conn)."<br />";
echo "Apache进程号:".getmypid();
?>


刷新下页面,MySQL那边开始刷show processlist,这回Time > 30也不会被kill,>60才被kill了,说明设置了MySQL_CLIENT_INTERACTIVE,就会被MySQL视为interactive connection,那么这次PHP的MySQL连接在120秒内未刷新的情况下,何时作废将取决于MySQL的interactive_timeout的配置值。

总结
PHP的mysql_pconnect要达到功效,首先必须保证Apache是支持keep alive的,其次KeepAliveTimeOut应该设置多久呢,要根据自身站点的访问情况做调整,时间太短,keep alive没啥意义,时间太长,就很可能为一个闲客户端连接牺牲很多服务器资源,毕竟hold住socket监听进程是要消耗cpu内存的。最后Apache的KeepAliveTimeOut配置得和MySQL的time out配置要有个平衡点,联系以上的观察,假设mysql_pconnect未带上第4个参数,如果Apache的KeepAliveTimeOut设置的秒数比wait_timeout小,那真正对mysql_pconnect起作用的是Apache而不是MySQL的配置。这时如果MySQL的wait_timeout偏大,并发量大的情况下,很可能就一堆废弃的connection了,MySQL这边如果不及时回收,那就很可能Too many connections了。可是如果KeepAliveTimeOut太大呢,又回到之前的问题,所以貌似Apache。KeepAliveTimeOu不要太大,但比MySQL。wait_timeout 稍大,或者相等是比较好的方案,这样可以保证keep alive过期后,废弃的MySQL连接可以及时被回收。


fastcgi并不是每个请求生成一个线程吧,fastcgi是会先生成一批php进程,每个请求过来,会分配到这些php进程进行处理。请求结束后,长连接是不会被回收的




本文固定链接: http://php.oneboys.cn/post-56.html

blogger
该日志由 老温 于2015-9-2 17:14 Wednesday发表在 攻城狮 分类下。
版权所有:《一小子攻城狮》 → 《mysql长连接》;
除特别标注,本博客所有文章均为原创. 互联分享,尊重版权,转载请以链接形式标明本文地址;
本文标签: MYSQL 长连接 mysql长连接apache mysql长连接fastcgi
上一篇::base64编码加密
下一篇:js结合iframe解决跨域问题

热门文章

相关文章

  • php取客户端IP
  • 左下角音乐播放器 支持网易云音乐歌单
  • 正则表达式替换网页中部分标签
  • 删除所有.svn目录,去除版本控制
  • js 判断是否微信小程序浏览器打开
取消回复

发表评论

亲,头像对么?

提交中,请稍候……



    最新文章热门文章随机文章

    • php邀请背景图合成二维码
    • 列表点击编辑
    • css 宽度不固定 正方形盒子
    • 终端生成证书 公私钥
    • apache环境接收自定义header
    • PHP curl 模块获取header和body完整信息
    • php导出excel,不用php Excel类
    • Mysql 导入错误1064 USING BTREE错误
    • PHP PDO属性列表以及PDO方法分类
    • mysql1153错误,max_allowed_packet
    • php文件处理相关 批量替换等等
    • apache环境接收自定义header
    • 终端生成证书 公私钥
    • css 宽度不固定 正方形盒子
    • 列表点击编辑
  • 标签

      PDO方法 PDO属性 MYSQL MYSQL重装 linux学习 linux命令screen php命令行 phpExcel max_allowed_packet Jquery php命名空间 MYSQL错误 php获取header信息 getallheaders php弱类型 php运算符优先级 自媒体运营 svn svn命令 解除svn控制 PHP 客户端IP sql随机取出数据 SQL语句limit qq群排名 QQ群排名技术 刷QQ群活跃度 .htaccess rar安装 301重定向 mysql_ping api错误码设计 PDO PDO连接状态 营销中的驱动媒介 curl获取header信息 CURL HTML+CSS checkbox的change事件 DOM加载 图片加载 微信公众账号 微信公众账号加粉 解除svn版本控制 html5 html5上传 html5进度条上传 高并发 队列 高并发超载 html5预览 MEMCACHE 一致性哈希算法 WDCP zendStudio php内置函数不能自动提示 insertinto selectinto 经典SQL语句大全 sql学习 Javascript 日期函数 获取月份天数 linux find命令 锚点 HTML 正则表达式 CSS自动折行 CSS自动换行 SQL CDN测试 哈夫曼 SQL删除重复并保留一条 curl下载 修改mysql root密码 php删除文件夹 php遍历文件夹 JS js时间戳 iframe跨域 js+iframe跨域传递参数 长连接 mysql长连接apache mysql长连接fastcgi base64编码加密 SSDB ssdb遍历集合 ssdb集合 emlog nginx伪静态 Fireworks 文字水印 前端 echarts echarts全国地图 html+css隐藏滚动条 魔方图片 魔方相册 html+css魔方相册 php错误日志 PhpSpreadsheet PhpSpreadsheet导入 PhpSpreadsheet导出 PhpSpreadsheet设置单元格属性 js函数抖动 中文分词 中文切词 php公众号 网易云音乐播放器 网易云音乐歌单播放器 解析header 解析cookie CSS
  • 存档

    • 2021年2月(2)
    • 2021年1月(3)
    • 2020年11月(11)
    • 2020年10月(3)
    • 2020年8月(1)
    • 2020年2月(9)
    • 2020年1月(2)
    • 2019年12月(3)
    • 2016年11月(1)
    • 2016年5月(1)
    • 2016年2月(1)
    • 2015年10月(2)
    • 2015年9月(2)
    • 2015年7月(9)
    • 2015年6月(5)
    • 2015年5月(1)
    • 2015年4月(4)
    • 2015年3月(13)
    • 2015年2月(3)
    • 2015年1月(8)
    • 2014年12月(10)
© 2010 - 2014 老温PHP 版权所有