TCP-超时重传

TCP协议是一种面向连接的有状态网络协议。对于发送的每个数据包,一旦TCP堆栈收到特定数据包的ACK,它就认为它已成功传递。

TCP使用指数退避超时重传一个未确认的数据包,最多tcp_retries2时间(默认为15),每次重传超时在TCP_RTO_MIN(200毫秒)和TCP_RTO_MAX(120秒)之间。一旦第15次重试到期(默认情况下),TCP堆栈将通知上面的层(即应用程序)断开连接。

tcp_retries2可以通过修改文件/proc/sys/net/ipv4/tcp_retries2sysctl net.ipv4.tcp_retries2进行调优。

TCP_RTO_MIN和TCP_RTO_MAX的值在Linux内核中硬编码,并由以下常量定义:

1
2
#define TCP_RTO_MAX ((unsigned)(120*HZ))
#define TCP_RTO_MIN ((unsigned)(HZ/5))

Linux2.6+使用1000毫秒的HZ,所以TCP_RTO_MIN是200毫秒,TCP_RTO_MAX是120秒。给定tcp_retries设置为15的默认值,这意味着在将断开的网络链路通知给上层(即应用程序)之前需要924.6秒,因为在最后一次(第15次)重试到期时检测到连接断开。

RetransmissionRTO(ms)Time before a timeout(sec)Time before a timeout(min)
12000.2 secs0.0 mins
24000.6 secs0.0 mins
38001.4 secs0.0 mins
416003.0secs0.1 mins
532006.2 secs0.1 mins
6640012.6 secs0.2 mins
71280025.4secs0.4 mins
82560051.0 secs0.9 mins
951200102.2 secs1.7 mins
10102400204.6 secs3.4 mins
11120000324.6 secs5.4 mins
12120000444.6 secs7.4 mins
13120000564.6 secs9.4mins
14120000684.6 secs11.4 mins
15120000804.6 secs13.4 mins
16120000924.6 secs15.4 mins

真正起到限制重传次数的并不是真正的重传次数。而是以tcp_retries2为boundary,以rto_base(如TCP_RTO_MIN 200ms)为初始RTO,计算得到一个timeout值出来。如果重传间隔超过这个timeout,则认为超过了阈值。实际TCP 的 RTO 是动态计算[1]的,也就是说:

  • 如果RTT比较小,那么RTO初始值就约等于下限200ms。由于timeout总时长是924600ms,表现出来的现象刚好就是重传了15次,超过了timeout值,从而放弃TCP流
  • 如果RTT较大,比如RTO初始值计算得到的是1000ms。那么根本不需要重传15次,重传总间隔就会超过924600ms。例如一个RTT=400ms的情况,当tcp_retries2=10时,仅重传了3次就放弃了TCP流

由于tcp_retries2是个系统级参数,在实际使用中,可以针对应用修改TCP_USER_TIMEOUT[2]

建议公式为(开启了 KeepAlive 的情况):

1
TCP_USER_TIMEOUT >= TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT

TCP_USER_TIMEOUT 是RFC 54288 规定的 TCP option,用来扩展 TCP RFC 7939 协议中本身的 “User Timeout” 参数(原协议不允许配置参数大小)。其用来控制已经发送,但是尚未被 ACK的数据包的存活时间,超过这个时间则会强制关闭连接。


  1. RTO的计算方法(基于RFC6298和Linux 3.10) ↩︎

  2. When TCP sockets refuse to die ↩︎