文章《TCP 的那些事 | 快速重传》、《TCP 的那些事 | SACK》及《TCP的那些事 | D-SACK》讲解了TCP的快速重传机制、SACK机制以及D-SACK机制。TCP超时与重传中最重要的部分就是对一个给定连接的往返时间RTT(Round-Trip Time)的测量。由于路由器和网络流量均会变化,RTT这个时间可能经常会发生变化,如果测量出来RTT,那么发送端大致就知道需要多久进行重传,这个重传时间就是RTO(Retransmission TimeOut)。
如果Timeout时间设置的太长,重发就慢,丢了老半天才重发,没有效率,性能差;设置太短,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。
最初的TCP规范使用低通过滤器来更新一个被平滑的RTT估计器(记为R),公式如下:
其中,α是一个推荐值为0.9的平滑因子。每次进行新测量的时候,这个被平滑的RTT将得到更新。每个新估计的90%来自前一个估计(为M),而10%则取自新的测量(公式右边的R)。
该算法在给定这个随RTT的变化而变化的平滑因子的条件下,RFC 793推荐的重传超时时间RTO(Retransmission TimeOut)的值为:
其中,β是一个推荐值为2的时延离散因子。
但是当RTT变化范围很大时,使用上述方法无法跟上这种变化,从而引起不必要的重传:当网络已经处于饱和状态时,不必要的重传会增加网络的负载,对网络而言这就像在火上浇油一样。
因此,RFC793给出了如下的SRTT(Smoothed RTT)的计算公式:
SRTT = α * SRTT+ (1- α) * RTT
其中,α 取值在0.8 到 0.9
RTO的计算公式如下:
RTO = min(UBOUND, max (LBOUND, β * SRTT))
UBOUND是最大的timeout时间,上限值
LBOUND是最小的timeout时间,下限值
β 值一般在1.3到2.0之间
上述计算公式需要面临一个问题:RTT怎么计算?是用第一次发数据的时间和Ack回来的时间做RTT样本值,还是用重传的时间和ACK回来的时间做RTT样本值。
1. 用第一次发数据的时间和Ack回来的时间做RTT样本值。但是对于图1(a)的情况(Ack没有及时回来,发送端进行了重传,但是后续Ack又回来了),存在计算值偏大的问题
2. 用重传时间和Ack回来的时间做RTT样本值。但是对于如图1(b)所示的情况(在重传包后不久就收到Ack)存在计算值偏小的问题
Karn's algorithm
针对RTT计算的问题,Karn's algorithm提出如下解决方法:不把重传的报文进行采样,于是就不用纠结重传和Ack之间的关系了。
但是这个方法存在的问题也很明显:如果网络突然发生剧烈变化,产生了很大的延时,但是由于之前的RTO很小,网络变动后又没有重新计算RTO,那么这个延时会导致重传之前所有的报文,网络情况本身就不好,又重传了很多报文,进而形成了恶性循环。
为了解决这个问题,Karn's algorithm算法采用了如下测量:只要重传,就将RTO翻倍。这个策略在具有高丢包率的网络环境中可以很好的平衡效率和性能。但是这个策略太死板,不能根据实际情况进行变化。
Jacobson / Karels Algorithm
前面两种算法用的都是“加权移动平均”,这种方法最大的毛病就是如果RTT有一个大的波动的话,很难被发现,因为被平滑掉了(这个也是加权平均的优点)。Jacobson / Karels Algorithm引入了最新的RTT的采样和平滑过的SRTT的差距做因子来计算。 公式如下:
SRTT = SRTT + α (RTT – SRTT)
DevRTT = (1-β)*DevRTT + β*(|RTT-SRTT|)
RTO= µ * SRTT + ∂ *DevRTT
其中,DevRTT(Deviation RTT),是SRTT和RTT的差距(加权移动平均)在Linux下,α = 0.125,β = 0.25, μ = 1,∂ = 4 ,这个算法在被用在今天的TCP协议中。
参考资料
2. TCP 的那些事儿(下)
3. 《TCP/IP详解 卷1》
4. Computing TCP's Retransmission Timer