问题现象

我边遇到的一个情况,一台主机访问另一台主机时,会发现有的时候时长特别长(注意是特别长),有的时间非常快,出现有问题的概率挺大的。通过抓包比对,发现TCP/IP包在花费时间特别长的时候有包重传的情况。而多花费的这3秒就耗费在了重传上:

full-nat-syn-ack
full-nat-syn-ack

问题解决

其实这个问题呢,是个老生常谈的问题,这里涉及到两个TCP/IP参数net.ipv4.tcp_timestamps=1 和 net.ipv4.tcp_tw_recycle=1(默认该值为0),当两个参数同时设置为1时,就会导致上面提到的问题。解决方法也很简单,设置其中一个参数为0即可。

tcp_timestamps的本质是记录数据包的发送时间。基本的步骤如下:

  • 发送方在发送数据时,将一个timestamp(表示发送时间)放在包里面
  • 接收方在收到数据包后,在对应的ACK包中将收到的timestamp返回给发送方(echo back)
  • 发送发收到ACK包后,用当前时刻now – ACK包中的timestamp就能得到准确的RTT。当然实际运用中要考虑到RTT的波动,因此有了后续的(Round-Trip Time Measurement)RTTM机制。

但是需要注意的是,timestamps是一个双向的选项,当一方不开启时,两方都将停用timestamps。比如client端发送的SYN包中带有timestamp选项,但server端并没有开启该选项。则回复的SYN-ACK将不带timestamp选项,同时client后续回复的ACK也不会带有timestamp选项。当然,如果client发送的SYN包中就不带timestamp,双向都将停用timestamp。

tcp_tw_recycle 从字面上也可以看出,就是timewait的快速回收,对客户端和服务器同时起作用,开启后在 3.5*RTO 内回收,RTO 200ms~ 120s 具体时间视网络状况。注意还有一个tcp_tw_reuse参数,该参数只对客户端起作用,开启后客户端在1s内回收。reuse就是重用time_wait的socket连接。 服务端同一个端口被连接理论上是没限制的。

tcp_tw_reuse,tcp_tw_recycle 必须在客户端和服务端timestamps 开启时才管用(默认打开),其实意思就是假如服务端和客户端两边有一边timestamps没开启。tcp_tw_reuse和tcp_tw_recycle都没啥作用。tcp_tw_recycle 内网状况比tcp_tw_reuse 稍快,公网尤其移动网络大多要比tcp_tw_reuse 慢,优点就是能够回收服务端的TIME_WAIT数量。

分析总结

本文提到的这个同时开启这两个参数在全NAT环境下出问题的机率较高,除了上面所说的请求时间长,还有可能会出现ping和telnet都是正常的,但是就是访问异常,无响应。

理解了这两个参数,问题原因就比较简单了:问题出在了tcp三次握手,因为net.ipv4.tcp_timestamps=1 标记了时间戳,如果有一个用户的时间戳大于这个链接发出的syn中的时间戳,服务器上就会忽略掉这个syn,不返会syn-ack消息,表现为用户无法正常完成tcp3次握手,从而不能打开web页面。在业务闲时,如果用户nat的端口没有被使用过时,就可以正常打开;业务忙时,nat端口重复使用的频率高,很难分到没有被使用的端口,从而产生这种问题。

**具体应该关闭和开启那个参数呢?**建议权限linux的默认设置,关闭tcp_tw_recycle,因为Linux已经优化很好了 。而当一台主机即冲当服务端,同时又是客户端时(比如web,对于用户来说是服务端,但对于后端的数据库或第三方应用来说就是客户端),想要解决timewait过多的问题,可以开启tcp_tw_reuse和调整ip_local_port_range可用范围。

另外tcp_timestamp还被用于PAWS机制,先解释下PAWS(Protect Against Wrapped Sequence numbers):在高带宽下,TCP序列号可能在较短的时间内就被重复使用(recycle/wrapped)就可能导致同一条TCP流在短时间内出现序号一样的两个合法的数据包及其确认包!而开启了tcp_timestamp后,就会:

  1. 发送方在发送数据时,将一个timestamp(表示发送时间)放在包里面
  2. 接收方在收到数据包后,在对应的ACK包中将收到的timestamp返回给发送方(echo back)
  3. 发送发收到ACK包后,用当前时刻now – ACK包中的timestamp就能得到准确的RTT

当然,开启timestamp也不是说没有一点坏处,其会增加10字节的TCP header开销。

参考页面:http://perthcharles.github.io/2015/08/27/timestamp-intro/