PHP資料庫長連線mysql_pconnect的細節
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,測試一下,趕緊寫行東西:
1<?php
2 echo “Apache程式號:”. getmypid();
3?>
很簡單,獲取當前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;`,看到了什麼?
[sql] view plaincopy
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]節點,在裡面設定多兩行
1 interactive_timeout = 60
2 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連線可以及時被回收。
最新內容請見作者的GitHub頁:http://qaseven.github.io/
相關文章
- PHP 連線access資料庫PHP資料庫
- PHP連線資料庫的步驟PHP資料庫
- PHP中的資料庫連線方法PHP資料庫
- php網站資料庫連線PHP網站資料庫
- PHP連線、查詢MySQL資料庫PHPMySql資料庫
- 精PHP與MYSQL資料庫連線PHPMySql資料庫
- 教你如何用php pdo連線資料庫PHP資料庫
- 【YashanDB資料庫】PHP無法透過ODBC連線到資料庫資料庫PHP
- PHP 遠端使用 PDO 連線 access 資料庫PHP資料庫
- 為什麼我的 PHP 資料庫連線失敗?PHP資料庫
- php連結資料庫PHP資料庫
- 用Navicat連線資料庫-資料庫連線(MySQL演示)資料庫MySql
- Visual Studio 2019連線MySQL資料庫詳細教程MySql資料庫
- php基礎之連線mysql資料庫和查詢資料PHPMySql資料庫
- 連線資料庫資料庫
- 資料庫的連線數資料庫
- php與資料庫連線如何實現資料的順序和倒序PHP資料庫
- 資料庫連線池-Druid資料庫連線池原始碼解析資料庫UI原始碼
- mysqli連線資料庫MySql資料庫
- Mongodb資料庫連線MongoDB資料庫
- Android 連線資料庫Android資料庫
- java連線資料庫Java資料庫
- 連線資料庫-mysql資料庫MySql
- jmeter連線資料庫JMeter資料庫
- Mybatis連線資料庫MyBatis資料庫
- JSP連線資料庫JS資料庫
- JDBC連線資料庫JDBC資料庫
- Flask連線資料庫Flask資料庫
- 資料庫與python的連線資料庫Python
- 資料庫的連線過程資料庫
- 《四 資料庫連線池原始碼》手寫資料庫連線池資料庫原始碼
- 【MySQL】自定義資料庫連線池和開源資料庫連線池的使用MySql資料庫
- Laravel,PHP 如何使用資料庫連線池提高效能LaravelPHP資料庫
- python 連線 mongo 資料庫連線超時PythonGo資料庫
- 織夢CMS(dedecms)的資料庫連線檔案_織夢連線資料庫檔案資料庫
- Java 資料庫連線的那些事Java資料庫
- 連線別人的MySql資料庫MySql資料庫
- 第77節:Java中的事務和資料庫連線池和DBUtilesJava資料庫
- Python連線SQLite資料庫PythonSQLite資料庫