time wait是TCP断开连接(即四次挥手)过程中的一个状态
发生时机
如上是网上找的两个图,分别是完整的TCP状态转换图和TCP四次挥手示意图。
首先界定一个概念:TCP是双工的,因此对单个连接而言不存在真正意义上的“服务端”和“客户端”之分,只要TCP连接建立了,对双方是对等的。 这里如果提到服务端和客户端,发起请求方就是客户端。
为了方便叙述,把四次挥手的过程分别称为“第X次挥手”
由上图可知,TIME WAIT发生在一方主动断开连接时。当主动断连的一方收到第三次挥手的时候,进入TIME_WAIT状态,随后发出第四次挥手的ACK。对于HTTP,服务器在主动断连时的返回里会带上header Connection: Close
。
被动断开方收到ACK后就会close,而主动断开方则会等待一段时间才会close。注意这里的“等待一段时间”,问题就出在这里。
为什么要等待 & 等多久
因为主动断开方还无法确定被动断开方是否收到了自己的第四次挥手。
假设第四次挥手没有被收到,那被动断开方还会再发送第三次挥手的FIN。换句话说,如果在“足够长”的时间内还没有收到重发的第三次挥手,那就证明对方已经收到了。这个“足够长”的时间一般设为2 x MSL。这样设置是因为一个来回的最长时间就是2 x MSL, 这样就能避免一个TCP连接已经断开,且同一个 ip+port已经被其它连接复用之后,又收到从旧连接发回的数据包。
查看等待时间:cat /proc/sys/net/ipv4/tcp_fin_timeout
有什么问题
一般Linux的MSL是60秒, 2MSL也就是120秒。
假设有个服务每秒有1000个新请求,而每处理一次TCP连接后就会主动断开,而这个连接要等120秒才会真正被断开,很快系统中就会积累大量处在TIME_WAIT的TCP连接。其结果就是系统中有大量处在 TIME_WAIT 状态的连接,在性能上的现象就是 CPU usage有所上升,CPU load明显上升。
需要注意的是, 大量 TIME_WAIT 不一定会导致有些人所说的“端口号被吃满”。如果是作为服务器产生大量TIME_WAIT, 不会有这个问题,只有客户端才可能出现65535个端口被占满的现象。
出现场景
前面有提到,出现time_wait的时机是“主动断开连接时”,而异常高的 time_wait会造成系统性能下降。下面列举几种见到过的这种场景。
没有使用长连接
如果一个HTTP是短连接,那么每次请求后都会断开,都会产生一个TIME_WAIT。但是都己经2023年了,绝大多数HTTP连接应该都是长连接,因此这种情况更可能是因为配置错误或漏配而产生的短连接。
但是,还是应该先检查确认整个TCP连接的全过程都是长连接。 HTTP请求中带header Connection: Keep-Alive
表示这个请求支持长连接。
也要确认服务器对于TCP长连接的配置参数是否合理,比如Nginx上的 keepalive_requests
命令,表示一个长连接能服务的最大请求数。如果这个值很低而QPS很高,也会造成比大量TIME_WAIT。
注意在使用nginx作为反向代理时,nginx 到upstream默认是短连接,需要主动加上相关配置来实现长连接。
请求阿里云SLB
详见 为何客户端突然出现大量TIME_WAIT堆积 - 知乎 (zhihu.com)
如果服务是向外发送大量请求,并且请求的目的地址是阿里云SLB,这个问题会导致即使配置了正确的参数也会失效。
参数与优化
列举相关的linux与nginx参数如下,并提出优化点 、
linux
查看方式: cat /etc/sysctl.conf
修改方式1: 直接修改上述配置文件后 /sbinsysctl -p
使修改生效
修改方式2: /sbin/sysctl -w {param}={value}
参数 | 意义 | |
---|---|---|
net.ipv4.tcp_keepalive_time | TCP发送keepalive探测消息的间隔时间(秒) | |
net.ipv4.tcp_keepalive_intvl | 探测消息未获得响应时,重发探测包的间隔时间(秒) | |
net.ipv4.tcp_keepalive_probes | 在认定TCP连接失效之前,最多发送多少个keepalive探测包 | |
net.ipv4.tcp_fin_timeout | 该时间间隔后关闭这个处在TIME_WAIT的连接 | |
net.ipv4.tcp_syncookies | 打开它可以一定程序上防止被洪水攻击 | |
net.ipv4.tcp_tw_reuse | 是否允许处在time_wait状态的连接被复用 | |
net.ipv4.tcp_tw_recycle | 不建议用,在新版本中已经废弃 | |
net.ipv4.tcp_max_tw_buckets | 允许的最大TIME_WAIT数量,如果TIME_WAIT数达到这个值了,后面的新TIME_WAIT会立刻close |
优化:
net.ipv4.tcp_fin_timeout
: 在现在网络环境普遍比较好的情况下,可以把这个值调小,比如10秒甚至更低。
net.ipv4.tcp_tw_reuse
:打开
net.ipv4.tcp_max_tw_buckets
:需要适中,避免过大。 如果发生了TIME_WAIT过多的问题,把它调小就可简单粗暴地缓解问题。很多讨论技术优化的文章都建议把这个值调高,但过高也会造成CPU负载升高。因此还是应找到一个适中的值。
nginx
参数 | 意义 |
---|---|
keepalive_timeout |
长连接的timeout, 0表示禁用长连接 |
keepalive_requests |
一个长连接能服务的最大请求数 |
请注意keepalive_request
这个参数。它的默认值是100,即当一个长连接上处理完100个请求时,服务端会强行关闭这个连接。
如果请求量大而keepalive_request
的值又小,就会造成较多连接被服务端强行关闭,反应出来的现象也是time_wait变多。
另外,如果有多个nginx共同使用同一个负载均衡器而他们的keepalive_requests
配置不同,也有可能造成负载不均。