3. RTO 计算与自适应算法:Jacobson、Karn 与 RTT 样本采集

RTO(Retransmission Timeout)重传超时时间,是 TCP 可靠传输的命门。设短了,网络一抖就疯狂重传,带宽全浪费;设长了,丢包半天才发现,用户卡到怀疑人生。所以 RTO 必须动态调整,不能写死。

这一节我们深入 RTO 的计算内核。我会把 Jacobson 算法、Karn 算法、RTT 样本采集与平滑这几个核心点串起来讲。你理解透了,以后抓包看到重传,一眼就能判断是 RTO 不合理还是真丢包。

3.1 RTT 样本采集——RTO 的原材料

RTO 不是凭空算的,它依赖 RTT(Round-Trip Time)样本。RTT 就是一个数据包从发出去到收到 ACK 的时间差。

但采集 RTT 样本有个坑:你该拿哪个包和哪个 ACK 配对?

早期实现很简单——每发一个数据段,记下时间 T1;收到 ACK 时,用当前时间 T2 减去 T1。但问题来了:如果发生了重传,这个 ACK 是对应第一次发的包,还是第二次发的?

⚠ 重传模糊性问题:收到 ACK 时,你无法确定它确认的是原始包还是重传包。用错样本会导致 RTT 计算严重失真。

我在项目中遇到过这样一个案例:某嵌入式设备在弱网环境下 RTO 越算越小,最后疯狂重传,把本来就差的链路彻底打满。查了半天,就是 RTT 样本采集时没处理重传歧义,把重传后的 ACK 当成了原始包的 RTT,导致样本偏小。

3.2 Karn 算法——解决重传歧义

Karn 算法就是专门解决上面那个问题的。它的核心思想很简单:

  • 发生重传时,忽略本次 RTT 样本,不参与 RTO 计算
  • 直到收到一个没有重传的段的 ACK,才恢复采样

说白了,就是「吃一堑,长一智」——既然分不清,那就不用了。等链路稳定了再说。

但 Karn 算法还有个配套机制:退避(Backoff)。当重传发生时,RTO 会指数增长(通常乘以 2),避免在链路已经拥塞时继续激进重传。

Karn 算法要点:
  • 重传期间不采集 RTT 样本
  • RTO 按指数退避增长
  • 收到非重传段的 ACK 后恢复采样,RTO 重新计算

嗯,这里要注意:Karn 算法只是解决了「样本该不该用」的问题,但 RTO 具体怎么算,还得靠 Jacobson 算法。

3.3 Jacobson 算法——RTO 的自适应计算

Jacobson 算法是 TCP RTO 计算的基石。它用指数加权移动平均(EWMA)来平滑 RTT,同时引入偏差项来应对抖动。

公式其实不复杂,我拆开讲:

SRTT = (1 - α) × SRTT + α × RTT_sample
RTTVAR = (1 - β) × RTTVAR + β × |SRTT - RTT_sample|
RTO = SRTT + 4 × RTTVAR

参数说明:

  • SRTT:平滑后的 RTT,初始值为第一个 RTT 样本
  • RTTVAR:RTT 偏差(抖动),初始值为 RTT_sample / 2
  • α:平滑因子,通常取 1/8(0.125)
  • β:偏差因子,通常取 1/4(0.25)
  • RTO:最终的超时时间,下限通常设为 1 秒,上限 60 秒

为什么是 4 倍的偏差?我个人习惯的理解是:在正态分布假设下,均值 ± 4 倍标准差能覆盖 99.99% 的情况。说白了,就是给网络抖动留足余量。

💡 实战经验:我在调优一个高并发网关时,发现默认的 RTO 下限 1 秒对于内网通信太长了。内网 RTT 通常只有 0.1-0.5ms,1 秒的 RTO 下限意味着丢包后要等 1 秒才重传。我建议根据实际场景调整 RTOmin,比如内网可以降到 200ms。

3.4 算法流程与决策逻辑

把 Jacobson 和 Karn 串起来,完整的 RTO 计算流程是这样的:

RTO 计算与自适应算法流程图 发送数据段,记录时间 T1 收到 ACK,记录时间 T2 判断:该段是否发生过重传? 是:丢弃该 RTT 样本 否:计算 RTT_sample = T2 - T1 RTO ×= 2(指数退避) 更新 SRTT 和 RTTVAR 计算 RTO = SRTT + 4 × RTTVAR 说明:Karn 算法负责判断样本是否可用(红色分支),Jacobson 算法负责计算 RTO(绿色分支)。两者配合使用。

3.5 参数对比与调优建议

不同场景下,RTO 参数的最佳取值差异很大。我整理了一张表,方便你对照:

参数 默认值 内网场景 跨洲链路 移动网络
α(平滑因子) 1/8 1/8 1/16(更平滑) 1/4(更快响应)
β(偏差因子) 1/4 1/4 1/8 1/2
RTOmin 1s 200ms 2s 1s
RTOmax 60s 10s 120s 60s
⚠ 调优警告:我曾经在某个项目中把 RTOmin 设成 50ms,结果内网是快了,但一旦出现轻微丢包,重传风暴直接把交换机打挂了。RTO 下限不是越小越好,要留足余量。

3.6 避坑指南

最后分享几个我踩过的坑:

  • 不要忽略初始 RTO:TCP 连接建立时的初始 RTO 通常为 3 秒(RFC 6298)。如果链路 RTT 很大(比如卫星链路),3 秒可能不够,建议根据实测调整。
  • RTO 计算不要用整数除法:SRTT 和 RTTVAR 是浮点或定点数,用整数除法会导致精度丢失,RTO 越算越偏。我见过一个嵌入式实现用整数除 8,结果 RTO 永远收敛不到正确值。
  • Karn 算法不是银弹:它只是避免了重传歧义,但代价是重传期间没有新样本,RTO 只能靠退避。如果链路持续抖动,RTO 会退避到很大,影响恢复速度。
  • 注意 RTT 样本的异常值:偶尔的延迟尖峰(比如 10 秒)会严重拉高 SRTT。我习惯在采集时做简单的异常过滤——超过 3 倍当前 SRTT 的样本直接丢弃。