在生产业务中,我们偶尔会碰到部分服务器相应比较慢,查看sockets会发现有大量的time_wait.今天我们就分析下这个请求的原因
1.问题现象
netstat -na | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
LISTEN 13
ESTABLISHED 9
FIN_WAIT1 1
TIME_WAIT 3207
#这里可以看到有3207个time_wait的链接
2.原理分析
首先我们需要知道socket
状态为TIME_WAIT
的原因是什么,这里我们需要回顾下TCP的四次挥手
明确下图中符号的含义
FIN
: 连接终止位seq
: 发送的第一个字节的序号ACK
: 确认报文段ack
: 确认号,希望收到的下一个数据的第一个字节的序号
刚开始双方都处于ESTABLISHED 状态,假设是客户端先发起关闭请求(这里的客户端是相对的)。四次挥手的过程如下:
第一次挥手: 客户端发送一个
FIN
报文(请求连接终止信号:FIN=1),报文中会指定一个序列号seq = u
.并停止在发送信息,主动关闭TCP
连接.此时客户端处于FIN_WAIT-1
状态,等待服务端的确认FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;
第二次挥手: 服务端收到
FIN
报文后,明确客户端想要断开连接.会发送ACK
报文,并且把客户端的seq
+1作为ACK
报文的序列号值,此时服务端处于CLOSE_WAITz
状态CLOSE-WAIT - 等待从本地用户发来的连接中断请求;
此时的 TCP 处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待 2)状态,等待服务端发出的连接释放报文段。
FIN-WAIT-2 - 从远程TCP等待连接中断请求;
第三次挥手: 如果服务端也要断开连接,没有数据需要传输了.会和客户端的第一次挥手一样,发送
FIN
报文,且随机指定一个序列号.然后服务端处于LAST_ACK
状态,等待客户端的确认LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;
第四次挥手: 客户端收到
FIN
之后,一样发送一个ACK应答报文(ack = w +1),且吧服务端的序列号+1作为seq
,此时客户端处于
TIME_WAIT
状态TIME-WAIT - 等待足够的时间以确保远程TCP接收到连接中断请求的确认;
这个时候由服务端到客户端的 TCP 连接并未释放掉,需要经过时间等待计时器设置的时间 2MSL(一个报文的来回时间) 后才会进入 CLOSED 状态(这样做的目的是确保服务端收到自己的 ACK 报文。如果服务端在规定时间内没有收到客户端发来的 ACK 报文的话,服务端会重新发送 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文给服务端)。服务端收到 ACK 报文之后,就关闭连接了,处于 CLOSED 状态。
通过这个流程我们可以发现有大量的TIME_WAIT
状态的socket
主要是客户端需要等待足够的时间确保服务端接受到请求
3.参数调优
根据上面分析TIME_WAIT
主要是客户端保证服务端能正常接受请求在等待的状态,默认这个是2ML
也就是4分钟,但是生产中这个配置建议可以更第一点30秒、1分钟、2分钟都是可以的
/etc/sysctl.conf
添加配置
#优化TIME_WAIT
net.ipv4.tcp_tw_reuse = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout = 30
#修改系統默认的TIMEOUT时间
sysctl -p
生效
稍等两分钟再次查看
netstat -na | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
LISTEN 13
ESTABLISHED 9
TIME_WAIT 18
已经大幅度减少了 降到了十位数