tw_reuse和tw_recycle 罪与罚


在使用腾讯云环境中,经常会遇到如下几种比较诡异的情况:

  1. CVM ping测试正常,但使用TCP连接,偶尔出现超时或延时较大,而此时网络并没有发生抖动。

  2. CLB+CVM架构经常会出现CLB检测到部分RS健康检查异常,但自己测试的时候发现RS正常服务。

  3. 用户访问CLB会出现504情况,查看日志发现是RS返回,而RS健康检查和测试都正常。

本文将针对上述出现场景提供一个解决思路。

Linux 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 = 60

    • 表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间

国外的一篇英文文档详细介绍了TCP TIME-WAIT 相关内容,题为《Coping with the TCP TIME-WAIT state on busy Linux servers》 有兴趣的同学可以移步参考。

net.ipv4.tcp_tw_reuse

TIME-WAIT状态是为了防止不相关的延迟请求包被接收。但在某些特定条件下,很有可能出现,新建立的TCP连接请求包,被旧的连接(同样的四元组,暂时还是TIME-WAIT状态,回收中)误处理。RFC 1323 实现了TCP扩展,以提高高带宽链路的性能。除此之外,它还定义了一个带有两个四字节时间戳字段的新TCP选项,第一个字节是TCP发送方的当前时钟时间戳,而第二个字节是从远程主机接收到的最新时间戳。启用net.ipv4.tcp_tw_reuse后,如果新的时间戳,比以前存储的时间戳更大,那么linux将会从TIME-WAIT状态的存活连接中,选取一个,重新分配给新的连接出去的TCP连接。此时处在TIME-WAIT状态连接,仅仅1秒后就可以被重用了。

作为客户端因为有端口65535限制的问题,TIME-WAIT状态的连接过多直接影响处理能力,打开tw_reuse 即可解决该问题。同时不建议打开tw_recycle,没有任何作用。net.ipv4.tcp_tw_reuse参数能够帮助客户端1s完成TCP连接的快速回收,此时单机就可实现6w/s的极限请求,如果还需要再增加连接数此时就需要增加IP数量来提高连接数。

net.ipv4.tcp_tw_recycle

TCP快速回收是一种链接资源快速回收和重用的机制,当TCP链接进入到TIME_WAIT状态时,通常需要等待2MSL的时长,但是一旦启用TCP快速回收,则只需等待一个重传时间(RTO)后就能够快速的释放这个链接,以被重新使用。

在一些高并发的网站服务器上,为了端口能够快速回收,打开了net.ipv4.tcp_tw_recycle。

net.ipv4.tcp_tw_recycle禁用时,kernal 是不会检查对端机器的包的时间戳。

但启用net.ipv4.tcp_tw_recycle后,kernel就会检查时间戳,如果发来的包的时间戳是乱跳的,就会出现把带了“倒退”的时间戳的包当作是recycle的tw连接的重传数据,不是新的请求,于是丢掉不回包,造成大量丢包。

当多个客户端处于同一个NAT环境时,同时访问服务器,不同客户端的时间可能不一致,此时服务端接收到同一个NAT发送的请求,就会出现时间戳错乱的现象,于是后面的数据包就被丢弃了,具体的表现通常是是客户端明明发送的SYN,但服务端就是不响应ACK。在服务器借助下面的命令可以来确认数据包是否有不断被丢弃的现象。

netstat -s | grep rejects

下面我们来具体说下,首先解释下 TCP 的 TIME_WAIT 状态:

  1. 通信双方建立TCP连接后,主动关闭连接的一方就会进入TIME_WAIT状态。比如,客户端主动关闭连接时,会发送最后一个ack后,然后会进入TIME_WAIT状态,再停留2个MSL时间,进入CLOSED状态。TIME_WAIT状态的作用是为了保证连接正常关闭,且不影响其他新建的链接。

TCP断开过程

  1. 开启net.ipv4.tcp_tw_recycle的目的,就是希望能够加快TIME_WAIT状态的回收,当然这个选项的生效也依赖于net.ipv4.tcp_timestamps的开启(缺省就是开启的)。当开启了net.ipv4.tcp_tw_recycle选项后,当连接进入TIME_WAIT状态后,会记录对应远端主机最后到达分节的时间戳。如果同样的主机有新的分节到达,且时间戳小于之前记录的时间戳,即视为无效,相应的数据包会被丢弃。

  2. 当客户终端经过NAT代理时,客户端TCP请求到达NAT网关,修改目的地址(IP+端口号)后便转发给后端服务器,而客户端时间戳数据没有变化。对于后端Web Server,请求的源地址是NAT网关,所以从后端服务器的角度看,原本不同客户端的请求经过NAT的转发,就可能会被认为是同一个连接,加之不同客户端的时间可能不一致,所以就会出现时间戳错乱的现象,于是后面的数据包就被丢弃了。

  • 注:
    重新修改回该值的初始值必须在 /etc/sysctl.conf 中修改net.ipv4.tcp_tw_recycle = 0 然后再执行命令:sysctl -p 之后才能生效。

总结

最合适的解决方案是增加更多的四元组数目,比如,服务器可用端口,或服务器IP,让服务器能容纳足够多的TIME-WAIT状态连接。在我们常见的互联网架构中(NGINX反代跟NGINX,NGINX跟FPM,FPM跟redis、mysql、memcache等),减少TIME-WAIT状态的TCP连接,最有效的是使用长连接,不要用短连接,尤其是负载均衡跟web服务器之间。

  • 在服务端,不要启用net.ipv4.tcp_tw_recycle

    • 除非能确保你的服务器网络环境不是NAT。在服务端上启用net.ipv4.tw_reuse对于连接进来的TCP连接来说,并没有任何用处。
  • 在客户端上启用net.ipv4.tcp_tw_reuse

    • 还算稍微安全的解决TIME-WAIT的方案。再开启net.ipv4.tcp_tw_recycle的话,对客户端(或以客户端形式)的回收,也没有什么用处,反而会发生很多诡异的事情(尤其是FPM这种服务器上,相对nginx是服务端,相对redis是客户端)。

可以使用如下命令查看当前主机的链接状态统计:

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

  • 相关链接

https://cloud.tencent.com/developer/article/1471414


文章作者: NesteaLin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 NesteaLin !
  目录