Skip to content

23 重传间隔有讲究 - 多久重传才合适

看了前面的重传的文章,你可能有一个疑惑,到底隔多久重传才是合适的呢?间隔设置比较长,包丢了老半天了才重传,效率较低。间隔设置比较短,可能包并没有丢就重传,增加网络拥塞,可能导致更多的超时和重发。

img

因此间隔多久重传就是不是一成不变的,它随着不同的网络情况需要动态的进行调整,这个值就是今天要介绍的「超时重传的时间」(Retransmission TimeOut,RTO),它与 RTT 密切相关,下面我们来介绍几种计算 RTO 的方法

01 经典方法(适用 RTT 波动较小的情况)

一个最简单的想法就是取平均值,比如第一次 RTT 为 500ms,第二次 RTT 为 800ms,那么第三次发送时,各让一步取平均值 RTO 为 650ms。经典算法的思路跟取平均值是一样的,只不过系数不一样而已。

经典算法引入了「平滑往返时间」(Smoothed round trip time,SRTT)的概念:经过平滑后的 RTT 的值,每测量一次 RTT 就对 SRTT 作一次更新计算

bash
SRTT = ( α * SRTT ) + ((1- α) * RTT)

img

α 是平滑因子,建议值是 0.8 ~ 0.9。假设平滑因子 α = 0.8,那么 SRTT = 80% 的原始值 + 20% 的新采样值。相当于一个低通滤波器。

  • 当 α 趋近于 1 时,1 - α 趋近于 0,SRTT 越接近上一次的 SRTT 值,与新的 RTT 值的关系越小,表现出来就是对短暂的时延变化越不敏感。
  • 当 α 趋近于 0 时,1 - α 趋近于 1,SRTT 越接近新采样的 RTT 值,与旧的 SRTT 值关系越小,表现出来就是对时延变化更敏感,能够更快速的跟随时延的变化而变化

超时重传时间 RTO 的计算公式是:

bash
RTO = min(ubound, max(lbound, β * SRTT))

其中 β 是加权因子,一般推荐值为 1.3 ~ 2.0。ubound 为 RTO 的上界(upper bound),lbound 为 RTO 的下界(lower bound)。

这个公式的含义其实就是,RTO 是一个 1.3 倍到 2.0 倍的 SRTT 值,最大不超过最大值 ubound,最小不小于最小值 lbound

img

这个算法下,平滑因子 α 取值范围是 0.8 ~ 0.9,RTT 对 RTO 的影响太小了,在相对稳定 RTT 的网络环境中,这个算法表现还可以,如果在一个 RTT 变化较大的环境中,则效果较差。

于是出现了新的改进算法:标准方法。

02 标准方法(Jacobson / Karels 算法)

传统方法最大的问题是 RTT 有大的波动时,很难即时反应到 RTO 上,因为都被平滑掉了。标准方法对 RTT 的采样增加了一个新的因素,

公式如下

bash
SRTT = (1 -  α) * SRTT +  α * RTT
RTTVAR = (1 - β) * RTTVAR + β * (|RTT-SRTT|)
RTO= µ * SRTT + * RTTVar

先来看第一个计算 SRTT 的公式

bash
SRTT = (1 -  α) * SRTT +  α * RTT

这个公式与我们前面介绍的传统方法计算 SRTT 是一样的,都是新样本和旧值不同的比例权重共同构成了新的 SRTT 值,权重因子 α 的建议值是 0.125。在这种情况下,SRTT = 87.5% 的原始值 + 12.5% 的新采样值。

img

第二个公式是计算 RTTVAR:「已平滑的 RTT 平均偏差估计器」(round-trip time variation,RTTVAR)

bash
RTTVAR = (1 - β) * RTTVAR + β * (|RTT-SRTT|)

img

平均偏差是标准方差的良好近似,计算较为容易,无需标准方差的求平方根运算。如果 β 取建议值 0.25 则

bash
RTTVAR
= 0.75 * RTTVAR + 0.25 * (|RTT-SRTT|)
= 75% 的原始值 + 25% 的平滑 SRTT 与最新测量 RTT 的差值

第三个公式计算最终的 RTO 值

bash
RTO = µ * SRTT + * RTTVAR

μ 建议值取 1,∂ 建议值取 4,则

bash
RTO = SRTT + 4 * RTTVAR

这种算法下 RTO 与 RTT 变化的差值关系更密切,能对变化剧烈的 RTT 做出更及时的调整。

03 重传二义性与 Karn / Partridge 算法

前面的算法都很精妙,但是有一个最基本的问题还没解决,如何重传情况下计算 RTT,下面列举了三种常见的场景

img

当客户收到重传过的某个请求的一个应答时,它不能区分该应答对应哪一次请求。

  • 如果用第一次发送数据的时间和收到 ACK 的时间来算 RTT,就会出现图 1 和图 2 中的问题,RTT 时间明显是大于实际值
  • 如果用第二次发送数据的时间和收到 ACK 的时间差值来算 RTT,就会出现图 3 中的问题,RTT 时间明显小于实际值

上面的这种问题,就称为「重传二义性」(retransmission ambiguity problem)

Karn / Partridge 算法就是为了解决重传二义性的。它的思路也是很奇特,解决问题的最好办法就是不解决它:

  • 既然不能确定 ACK 包到底对应重传包还是非重传包,那这次就忽略吧,这次重传的 RTT 不会被用来更新 SRTT 及后面的 RTO
  • 只有当收到未重传过的某个请求的 ACK 包时,才更新 SRTT 等变量并重新计算 RTO

仅仅有上面的规则是远远不够的,放弃掉重传那次不管看起来就像遇到危险把头埋在沙子里的鸵鸟。如果网络抖动,倒是突然出现大量重传,但这个时候 RTO 没有更新,就很坑了,本身 RTO 就是为了自适应网络延迟状况的,结果出问题了没有任何反应。这里 Karn 算法采用了出现重传就将 RTO 翻倍的方法,这就是我们前面看到过的指数级退避(Exponential backoff)。这种方式比较粗暴,但是非常简单。

04 小结

这篇文章我们讲了 RTO 的由来和计算 RTO 的经典方法和标准方法的计算方式:

  • 经典方法:适用 RTT 波动较小的情况
  • 标准方法:对 RTT 波动较大的情况下有更好的适应效果

最后的部分引入了「重传二义性」的概念,看到了计算重传情况下 RTT 的困难之处,由此引入了 Karn 算法:

  • 重传情况下不用测量的 RTT 来更新 SRTT 和 RTTVAR
  • 出现重传时 RTO 采用指数级退避的方式,直到后续包出现不需要重传就可以收到确认为止