Last-Modified: 2019年7月10日21:58:43php
項目生產環境出現大量TIME_WAIT(數千個), 須要一一排查前端
先上總結:nginx
統計TIME_WAIT 鏈接的本地地址web
netstat -an | grep TIME_WAIT | awk '{print $4}' | sort | uniq -c | sort -n -k1 # ... 前面不多的略過 # 2 127.0.0.1:56420 # 442 192.168.1.213:8080 # 453 127.0.0.1:9000
分析:redis
通過確認, nginx 的配置文件中存在一行後端
# 不啓用 keep-alive keepalive_timeout 0;
嘗試抓取 tcp 包服務器
tcpdump tcp -i any -nn port 8080 | grep "個人ip" # 其中某一次鏈接的輸出以下 # 20:52:54.647907 IP 客戶端.6470 > 服務端.8080: Flags [S], seq 2369523978, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0 # 20:52:54.647912 IP 服務端.8080 > 客戶端.6470: Flags [S.], seq 1109598671, ack 2369523979, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0 # 20:52:54.670302 IP 客戶端.6470 > 服務端.8080: Flags [.], ack 1, win 256, length 0 # 20:52:54.680784 IP 客戶端.6470 > 服務端.8080: Flags [P.], seq 1:301, ack 1, win 256, length 300 # 20:52:54.680789 IP 服務端.8080 > 客戶端.6470: Flags [.], ack 301, win 123, length 0 # 20:52:54.702935 IP 服務端.8080 > 客戶端.6470: Flags [P.], seq 1:544, ack 301, win 123, length 543 # 20:52:54.702941 IP 服務端.8080 > 客戶端.6470: Flags [F.], seq 544, ack 301, win 123, length 0 # 20:52:54.726494 IP 客戶端.6470 > 服務端.8080: Flags [.], ack 545, win 254, length 0 # 20:52:54.726499 IP 客戶端.6470 > 服務端.8080: Flags [F.], seq 301, ack 545, win 254, length 0 # 20:52:54.726501 IP 服務端.8080 > 客戶端.6470: Flags [.], ack 302, win 123, length 0
上述具體的ip已經被我批量替換了, 不方便暴露服務器ip
分析:併發
修改 nginx 配置負載均衡
keepalive_timeout 65;
reload nginxtcp
nginx -s reload
再次抓包
tcpdump tcp -i any -nn port 8080 | grep "個人ip" # 21:09:10.044918 IP 客戶端.8217 > 服務端.8080: Flags [S], seq 1499308169, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0 # 21:09:10.044927 IP 服務端.8080 > 客戶端.8217: Flags [S.], seq 2960381462, ack 1499308170, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0 # 21:09:10.070694 IP 客戶端.8217 > 服務端.8080: Flags [.], ack 1, win 256, length 0 # 21:09:10.077437 IP 客戶端.8217 > 服務端.8080: Flags [P.], seq 1:302, ack 1, win 256, length 301 # 21:09:10.077443 IP 服務端.8080 > 客戶端.8217: Flags [.], ack 302, win 123, length 0 # 21:09:10.198117 IP 服務端.8080 > 客戶端.8217: Flags [P.], seq 1:671, ack 302, win 123, length 670 # 21:09:10.222957 IP 客戶端.8217 > 服務端.8080: Flags [F.], seq 302, ack 671, win 254, length 0 # 21:09:10.222980 IP 服務端.8080 > 客戶端.8217: Flags [F.], seq 671, ack 303, win 123, length 0 # 21:09:10.247678 IP 客戶端.8217 > 服務端.8080: Flags [.], ack 672, win 254, length 0
注意看上面頗有意思的地方:
再次查看鏈接狀態
netstat -an | grep TIME_WAIT | awk '{print $4}' | sort | uniq -c | sort -n -k1 # ...忽略上面 # 1 127.0.0.1:60602 # 1 127.0.0.1:60604 # 344 127.0.0.1:9000
此時發現已經沒有處於 TIME_WAIT 的鏈接了.
通過網上查找資料, 整理:
當前 nginx 配置
upstream phpserver{ server 127.0.0.1:9000 weight=1; }
修改nginx配置使其與fastcgi的鏈接使用長鏈接
upstream phpserver{ server 127.0.0.1:9000 weight=1; keepalive 100 } fastcgi_keep_conn on;
說明:
keepalive
指定nginx每一個worker與fastcgi的最大長鏈接數, 當長鏈接不夠用時, 此時新創建的鏈接會在請求結束後斷開(因爲此時指定了 HTTP1.1, fastcgi不會主動斷開鏈接, 所以nginx這邊會出現大量 TIME_WAIT, 需謹慎(未驗證)keepalive
數量指定 100 (未測試)此處題外話, 若是 nginx 是做爲反向代理, 則需增長以下配置:
# 將http版本由1.0修改成1.1 proxy_http_version 1.1; # 清除"Connection"頭部 proxy_set_header Connection "";
proxy_pass
將請求轉發給後端這裏, 理解一下 proxy_pass
與 fastcgi_pass
區別
客戶端 --http--> 前端負載均衡Nginx --proxy_pass--> 業務服務器Nginx --fastcgi_pass--> 業務服務器 php-fpm
再次確認 tcp 鏈接狀況
netstat -antp | grep :9000 | awk '{print $(NF-1)}' | sort | uniq -c # 6 ESTABLISHED # 1 LISTEN
ok, 問題解決.
另外一種解決方法:
若 nginx 與 fast-cgi 在同一臺服務器上, 則使用 unix域 會更爲高效, 同時避免了 TIME_WAIT 的問題.
通過上面優化後, TIME_WAIT數量從上千個大幅降低到幾十個, 此時發現TIME_WAIT中的存在大量的 127.0.0.1:6379, 6379是redis服務的默認端口....
趕忙改業務代碼去, 將 $redis->connect(...)
改爲 $redis->pconnect(...)
說明:
pconnect
表示 php-fpm 與 redis 創建 tcp 鏈接後, 在本次http請求結束後仍維持該鏈接, 下次新的請求進來時能夠複用該鏈接, 從而複用了tcp鏈接.