0x00 問題
一個(gè)服務(wù)器運(yùn)行用nginx的web服務(wù)哄陶,PHP訪問mysql;由于php需要頻繁的訪問數(shù)據(jù)庫,而且使用的都是短鏈接,因此一段時(shí)間內(nèi)產(chǎn)生并保持大量的TIME_WAIT卸奉。
$ netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
LAST_ACK 11
LISTEN 11
SYN_RECV 42
ESTABLISHED 172
FIN_WAIT1 13
FIN_WAIT2 11
CLOSING 1
SYN_SENT 38
TIME_WAIT 3792
0x01 危害
系統(tǒng)長期保持大量的TIME_WAIT,相應(yīng)的會占用大量的系統(tǒng)資源,同時(shí)還會占用大量的端口和連接數(shù)尚粘,導(dǎo)致新來的請求無法被服務(wù)器及時(shí)響應(yīng)處理择卦;默認(rèn)情況下敲长,linux臨時(shí)端口號范圍是(32768,61000)郎嫁,本機(jī)可用于調(diào)用的端口約3萬個(gè),進(jìn)而導(dǎo)致調(diào)用后端服務(wù)阻塞祈噪,頁面響應(yīng)變慢泽铛;
0x02 解決辦法
根據(jù)以上分析,需要對系統(tǒng)內(nèi)核參數(shù)進(jìn)行優(yōu)化辑鲤,啟用TIME_WAIT連接重用盔腔,TIME_WAIT連接回收、縮短連接保持時(shí)間、增加可用端口數(shù):
vi /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1 # 連接重用
net.ipv4.tcp_tw_recycle = 1 # 連接回收
net.ipv4.tcp_fin_timeout = 30 # 連接保持時(shí)間
net.ipv4.ip_local_port_range=1024 65000 # 增加端口數(shù)量
sysctl -p # 生效
通常情況下弛随,設(shè)置前三條即可瓢喉,減少了TIME_WAIT數(shù)量,端口占用也會緩解舀透,但如果服務(wù)的并發(fā)量本來就很大栓票,增加端口數(shù)量就很有必要;
優(yōu)化生效后愕够,TIME_WAIT明顯減少:
$ netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
LAST_ACK 17
LISTEN 11
SYN_RECV 25
ESTABLISHED 107
FIN_WAIT1 9
FIN_WAIT2 23
CLOSING 2
SYN_SENT 14
TIME_WAIT 217
0x03 注意
需要確認(rèn)
net.ipv4.tcp_timestamps = 1
是打開的(默認(rèn)是打開的)走贪,因?yàn)橹挥衪imestamps打開了,net.ipv4.tcp_tw_recycle = 1
才會生效惑芭;當(dāng)
net.ipv4.tcp_timestamps
和net.ipv4.tcp_tw_recycle
同時(shí)打開坠狡,對通過NAT網(wǎng)關(guān)訪問服務(wù)器的連接會有問題,因?yàn)?tcp_tw_recycle/tcp_timestamps
都開啟的條件下遂跟,60s內(nèi)同一源ip主機(jī)的socket connect請求中的timestamp必須是遞增的逃沿。而timestamp時(shí)間為系統(tǒng)啟動(dòng)到當(dāng)前的時(shí)間,因此幻锁,不同客戶端的timestamp不相同感挥,timestamp 大的客戶端可以正常訪問,timestamp小的客戶端則訪問失斣桨堋触幼; 這種情況建議關(guān)閉tcp_tw_recycle
選項(xiàng),而不是timestamp
究飞;因?yàn)?在tcp timestamp關(guān)閉的條件下置谦,開啟tcp_tw_recycle是不起作用的;而tcp timestamp可以獨(dú)立開啟并起作用亿傅。
0x04 總結(jié)
使用過多的短連接媒峡,導(dǎo)致了大量的TIME_WAIT;在服務(wù)設(shè)計(jì)時(shí)葵擎,應(yīng)盡量采用長連接谅阿,連接池,KEEPALIVE等技術(shù)減少對后端的連接次數(shù)酬滤,提高連接的效率签餐。
這次的問題,因PHP本身的特性盯串,一個(gè)頁面處理完成后氯檐,所有相關(guān)連接就斷了,不太好使用長連接体捏,連接池冠摄,KEEPALIVE技術(shù)糯崎;
0x05 確有頻繁訪問時(shí),以上設(shè)置并不能減少 TIME_WAIT
當(dāng)服務(wù)器卻又很多用戶頻繁訪問河泳,以上針對系統(tǒng)的優(yōu)化并不能達(dá)到效果沃呢。
原因試試php頁面訪問mysql采用的是短連接,用完就斷開拆挥,頻繁訪問必然產(chǎn)生很多TIME_WAIT樟插;
解決辦法, 采用長連接的方式訪問數(shù)據(jù)庫竿刁,這里采用php的PDO擴(kuò)展訪問mysql黄锤,長連接還能提高性能:
lang="php">$db_host="localhost:3306";
$db_username="user";
$db_password="";
$dsn = "mysql:host=$db_host;dbname=$database;";
try {
$sql = new PDO($dsn,$db_username,$db_password,array(PDO::ATTR_PERSISTENT => true));//PDO::ATTR_PERSISTENT => true 表示采用持久化連接;
} catch(PDOException $e){
die($e->getMessage());
}
try{
$result = $sql->query("select * from accounts where id = '".$sn."';");
} catch(PDOException $e){
die($e->getMessage());
}
$rows = $result->fetchAll(PDO::FETCH_ASSOC);// 這里要注意食拜,查詢結(jié)果只能調(diào)用一次fetch鸵熟,后面再調(diào)用就返回空了,所以這個(gè)地方第一次調(diào)用就要保存下來給后續(xù)使用负甸;
if(count($rows) == 1){
$row = $rows[0];// 多個(gè)結(jié)果時(shí)流强,foreach 遍歷即可
.....
}
或者:
foreach($dbh->query('SELECT * from FOO') as $row) {
print_r($row);
}
- php7.2 設(shè)置支持PDO擴(kuò)展
vim /etc/php/7.2/cli/php.ini
這個(gè)配置文件是配置php命令行運(yùn)行環(huán)境;
vim /etc/php/7.2/fpm/php.ini
這個(gè)配置 php-fpm呻待,里面 extension=pdo_mysql 是注釋掉的打月,但是 /etc/php/7.2/fpm/conf.d/20-pdo_mysql.ini
中有定義,所以應(yīng)該是默認(rèn)支持pdo的蚕捉;</pre>
打開注釋: extension=pdo_mysql 即可奏篙;
系統(tǒng)優(yōu)化后,再使用PDO長連接訪問mysql迫淹,TIME_WAIT連接數(shù)秘通,直線下跌幾乎沒有;