1 超时估计
对于tcp发送端发送完数据后却迟迟收不到对端的ack报文,这个时候tcp连接应该怎么做呢?TCP 的对策是“超时重传”:在一定的时间内,如果还没有收到对方相应的 ACK 报文,那么 TCP 发送方会将这部分数据重新发送给对方。超时重传的机制没有问题,问题是“超时”的事件确定,也就是说,TCP 发送方等多少时间认为是超时了?如果超时时间过长,则发送效率有问题(时间都花在不必要的等待上了),如果超时时间过短,则会产生不必要的重复发送。要知道未能及时收到对方的 ACK 报文,很大的原因是因为网络拥塞,而这种迫不及待所产生的大量的不必要的重复发送,会更加加剧网络拥塞,从而造成恶性循环。
由于在不同时间网络拥塞情况不一样,因此不同时间点网络超时时间也不同。为了能做到随时更新,RFC 793、RFC 6298、RFC 1323 都分别提出了各自的 TCP 超时估计算法。在介绍这些算法之前,我们先看看 TCP 超时估算的本质。
1.1 TCP超时估算本质
先介绍一个名词:RTT。Round Trip Time:意思就是一对报文的往返时间,如下图5-119所示:
上图5-119中,T1.1 时刻,A 给 B 发送了1个报文(SEG1),T1.2 时刻,A 收到了 B 对应的 ACK 报文,这样的一来一往,称为一对报文,而 T1.2 - T1.1 这个时间差,也就称为 RTT。
第2个要介绍的名词是 RTO(Retransmission Timeout,重传超时)——也就是 TCP 在发送1个报文以后,所需要等待的时间。如果在这个时间段内一直没有收到对方的相应的 ACK 报文,那么就需要对该报文进行重传。TCP 一直不知道 RTO 应该等于多少,但是 TCP 还必须对 RTO 做一个估算,因为它发送1个报文以后,必须要设置1个重传超时的时间。而估算 RTO(Retransmission Timeout,重传超时)的算法,就是基于以前的 RTT,对下一次 RTT 做一个预测。
总结一下,TCP 超时估算的本质,就是认为未来网络的变化,与网络过去的变化,有一定的相关性。事实上,如果相隔时间不长(图5-120中的 Tn.1 与 Tn-1.1 的时间间隔),这种相关性存在的概率是比较大的。
1.2 RFC 793 的超时估算算法
RFC 793 的超时估算算法分为两部分:(1)对于 RTT 的估算;(2)基于估算的 RTT,计算 RTO。RFC 把对于 RTT 的估算,称为 SRTT(Smoothed Round Trip Time,平滑往返时间),为什么要用“平滑”这个单词呢,我们先看看它的估算公式:
- SRTTi+1 = ɑ * SRTTi + (1 - ɑ) * RTTi, i = 1, 2, 3 ...
其中:初始值 SRTT1 是一个经验值,比如等于 60(秒);ɑ 是一个常数,也是一个经验值,介于 0.8~0.9 之间。总的一句话来说就是加权平均。
根据上面公式进一步推导:
SRTTn = ɑn-1 * SRTT1 + ɑn-2 * (1 - ɑ)RTT1 + ɑn-3 * (1 - ɑ) * RTT2 + ɑn-1-i * (1 - ɑ) * RTTi + ɑ0 * (1 - ɑ) * RTTn-1= ɑn-1 * SRTT1 + (1 - ɑ) * Σɑn-1-i * RTTi, i ∈ [1, n-1]
根据以上所推导的 SRTTn 的公式,可以看出:
- (1)这个公式,其实用大白话说就是将历史上的所有 RTT 加权平均,这也就是所谓的“平滑”的含义吧,ɑ 也被称为平滑因子(smoothing factor);
- (2)因为 ɑ 介于 0.8~0.9 之间,所以越“古老”的 RTT,其权重越低;
RFC 估算出了 SRTT 以后,它还要经过一道转换,才得出 RTO:
RTO = min[UBOUND, max[LBOUND,(β * SRTT)]]
其中 UBOUND、LBOUND、β 都是经验数值,分别等于:60秒、1秒、1.3~2.0之间,当然,你也可以有自己的经验数值。
1.3 RFC 6298 的超时估算算法
在 RFC 793 中,RTO 的计算公式为:RTO = min[UBOUND, max[LBOUND,(β * SRTT)]];
如果 β * SRTT 的最小值大于1秒,最大值小于60秒,那么根据RFC793,RTO 的计算公式可以简化为:(假设 β = 1.3)
RTO = β * SRTT = 1.3 * SRTT = SRTT + 0.3 * SRTT
上述的算法仅仅只是引入了β这么一个常数来进行修正,其实质并不能反映网络的变化状态。而RFC 6298 的算法,正是对 RFC 793 的算法的一个改进:它真正将方差这一因素考虑了进去,而不是仅仅用一个经验系数进行修正。RFC 6298 算法又称 Jacobson/Karels 算法。
(1)当 RTT 还没有被测量出来时,令 RTO = 1(秒)。以前的 RFC(RFC 2988)定义 RTO = 3(秒),而且有的 TCP 的具体的实现,仍然可能会采用3(秒)。不过我们不纠结于细节,这里就认为 RTO = 1(秒)
(2)当第1个 RTT 被测量出来以后,令:
- SRTT1 = RTT1
- RTTVAR1 = RTT1 / 2
- RTO2 = SRTT1 + K * RTTVAR1 ----(X)
其中:K 是一个经验数据,等于4;RTTVAR 是 round-trip time variation(往返时间变化)的缩写,它的本意是要借用方差(variance)这一概念(下文会介绍 RTTVAR 与方差之间的关系)。也正是由于 RTTVAR 借用了方差这一概念,RTO 的预测(RTO2)也就有了方差的味道。
(3)当后续的 RTT 继续被测量出来时,计算公式如下:
- RTTVARi+1 = (1 - β) * RTTVARi + β * |SRTTi - RTTi| ----(A)
- SRTTi+1 = (1 - ɑ) * SRTTi + alpha * RTTi ----(B)
- RTOi+1 = SRTTi+1 + K * RTTVARi+1 ----(C)
其中,ɑ、β、K 都是经验数据,它们分别等于:1/8、1/4、4。这3个经验数据很有意思,它们虽然是一个经验值,但是非常“准确”,而且在计算机计算时,还可以通过移位来实现乘法,从而提高计算效率。
我们总结一下 RFC 793 和 RFC 6298 的公式:
- RT0 = β * SRTT ----RFC 793(简化表述)
- RT0 = SRTT + K * RTTVAR ----RFC 6298
(1)RFC 793,对 RTT 做了平滑处理得到了 SRTT,SRTT 反应了网络的动态变化。但是 RFC 793 仅仅是对 SRTT 的偏差做了一个简单的修正(β * SRTT),这个简单修正没有反应出网络的动态变化。
(2)RFC 6298 除了对 RTT 做了平滑处理(SRTT),反应了网络的动态变化(这一点与 RFC 793相同),而且还对 SRTT 的偏差(均方差的近似公式)做了平滑处理,并且以这个平滑的偏差(RTTVAR)对 SRTT 进行修正,这个修正也反应了网络的动态变化。
所以说,RFC 6298 比 RFC 793 更加准确、更加能反应网络的动态变化。另外还补充两点:第1点,与 RFC 793 一样,RFC 6298也规定了 RTO 的最小值(比如1秒)和最大值(比如60秒)。第2点,RFC 6298还考虑了测量 RTT 的时钟精度。
1.4 Karn 算法
Karn 算法并不是一个完整的估算 TCP 超时的算法,它是对前文所描述的算法的补充。无论是 RFC 793 还是 RFC 6298,在测量 RTT 时,都没有考虑“超时-重传-ACK”的情形,如下图5-122所示:
上图5-112中:T1 时刻,A 给 B 发送了1个报文 SEG1;T2 时刻,A 还没有收到 B 的 ACK 报文,认为超时了,于是又重新发送了 SEG1;T3 时刻,A 收到了 B 针对 SEG1的 ACK 报文。现在问题来了:T3 时刻的 ACK 报文,是针对 T1 时刻的 SEG1 的回应,还是针对 T2 时刻的 SEG1 的回应? 对于这种情况,业界公认的算法是 Karn 算法。
Karn 算法也没有纠结于那个 ACK报文到底是针对谁的回应(因为确实是搞不清楚),它是直接将 RTO 的估值采用指数回退的方法翻倍。所谓指数回退,就是发现1次超时,就将 RTO 乘以2,再发现1次超时,再将 RTO 乘以2。不过更准确的说法,Karn 算法应该是将 SRTT 进行指数回退,因为参与迭代的是 SRTT 而不是 RTO。