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链接.