程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> PHP數據庫長連接mysql_pconnect的細節

PHP數據庫長連接mysql_pconnect的細節

編輯:關於PHP編程

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函數)

	#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連接可以及時被回收。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved