對於做爲Apache模塊運行的PHP來講,要實現MySQL持久化鏈接,首先得取決於Apache這個web服務器是否支持Keep-Alive。 php
Keep-Alive mysql
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
2 |
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函數) 瀏覽器
02 |
user=php_get_current_user();//獲取當前PHP執行者(Apache)的進程惟一標識號 |
03 |
//hashed_details就是hash key |
04 |
hashed_details_length = spprintf(&hashed_details, 0,"MySQL__%s_", user); |
05 |
#2.若是未找到已有資源,就推入hash表,名字叫persistent_list,若是找到就直接使用 |
06 |
/* try to find if we already have this link in our persistent list */ |
07 |
if(zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)==FAILURE) { |
11 |
/* hash it up(推入hash表) */ |
12 |
Z_TYPE(new_le) = le_plink; |
14 |
if(zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) { |
20 |
{/* The link is in our list of persistent connections(鏈接已在hash表裏)*/ |
23 |
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代碼 併發
2 |
$conn= mysql_pconnect("localhost","root","123456")ordie("Can not connect to MySQL"); |
3 |
echo"MySQL線程號:". MySQL_thread_id($conn)."<br />"; |
4 |
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
1 |
mysql> show processlist; |
2 |
+-----+------+-----------+------+--------+-----+------+-----------------+ |
3 |
| Id | User | Host | db | Command| Time| State| Info | |
4 |
+-----+------+-----------+------+--------+-----+------+-----------------+ |
5 |
| 348 | root | localhost | NULL | Query | 0| NULL | show processlist| |
6 |
| 349 | root | localhost | NULL | Sleep | 2| | NULL | |
7 |
+-----+------+-----------+------+--------+-----+------+-----------------+ |
發現了很重要的信息,這個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]節點,在裏面設置多兩行
1 |
interactive_timeout = 60 |
配置完後,重啓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個參數改改呢
2 |
$conn= mysql_pconnect('localhost','root','123456',MySQL_CLIENT_INTERACTIVE); |
3 |
echo"MySQL線程號:".MySQL_thread_id($conn)."<br />"; |
4 |
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鏈接能夠及時被回收。