PHP的MySQL持久化链接,美好的目标,却拥有糟糕的口碑,每每使人敬而远之。这究竟是为啥么。近距离观察后发现,这家伙也不容易啊,要看Apache的脸色,还得听MySQL指挥。php
对于做为Apache模块运行的PHP来讲,要实现MySQL持久化链接,首先得取决于Apache这个web服务器是否支持Keep-Alive。mysql
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秒)。ios
好了,重启Apache,测试一下,赶忙写行东西:web
<!-- lang: php --> <?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进程。sql
浏览器是怎么知道不须要从新进行TCP链接就能够直接发送http请求呢?由于http返回头里就会带上Connection:keep-alive,Keep-alive:15两行,意思就是让客户端浏览器明白,此次socket链接我这边还没关闭呢,你能够在15内继续使用这个链接,并发送http请求,因而乎浏览器就知道应该怎么作了。shell
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代码并发
<!-- lang: 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;',看到了什么?socket
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个参数改改呢
<!-- lang: php --> <?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链接能够及时被回收。