2. RTT 与 RTO 基础:网络延迟的度量与超时决策

聊到连接超时和重传,有两个概念你绕不开——RTT 和 RTO。我刚开始做网络编程那会儿,总觉得这俩就是“延迟”和“超时”的简单对应。后来线上出了几次故障,才明白这里面的门道比想象中深得多。

说白了,RTT 是客观存在的网络延迟,RTO 是我们主观设定的等待阈值。两者之间的关系,就像天气预报和出门带伞——预报准了,你就能从容应对;预报偏了,要么淋雨,要么白背一路伞。

2.1 RTT 的定义:数据包往返的真实时间

RTT(Round-Trip Time),全称往返时间。它指的是一个数据包从发送端出发,到达接收端,再让接收端的确认包(ACK)回到发送端,这一整趟所花的时间。

你可以把它想象成你给朋友发了一条,朋友看到后回你一个“收到”,从你发出到看到“收到”之间的时间差,就是一次 RTT。

在 TCP 协议里,RTT 的测量单位通常是毫秒(ms)。一个典型的局域网 RTT 可能只有 0.5ms,跨洋链路可能高达 200ms 甚至更高。

核心要点:RTT 是动态变化的,受网络拥塞、路由跳数、物理距离、中间设备处理延迟等多种因素影响。同一个连接的不同时刻,RTT 可能完全不同。

2.2 RTO 的定义:我们愿意等多久

RTO(Retransmission Timeout),即重传超时时间。它是发送端在发出一个数据包后,等待 ACK 的最长时间。如果在这个时间内没收到确认,发送端就认为这个包丢了,于是触发重传。

我习惯把 RTO 比作“耐心阈值”。你约了朋友在咖啡馆见面,等了 10 分钟没来,你可能觉得他堵车;等了 30 分钟,你可能开始打电话;等了 1 小时,你可能直接走了——这个“1 小时”就是你的 RTO。

在 TCP 实现中,RTO 不是一个拍脑袋定的值。它必须基于 RTT 计算出来,而且每次 RTT 变化时,RTO 都要跟着调整。

概念 含义 单位 谁决定的
RTT 数据包往返实际耗时 毫秒(ms) 网络状况(客观)
RTO 发送端等待 ACK 的超时阈值 毫秒(ms) 发送端根据 RTT 计算(主观)

2.3 RTT 与 RTO 的关系:动态适配的父子关系

RTO 不是凭空产生的,它直接依赖于 RTT 的测量结果。你可以这样理解:RTO = f(RTT),RTO 是 RTT 的函数。

但这里有个关键问题——RTT 是波动的,而 RTO 必须是一个相对稳定的值,不能跟着 RTT 的每一次抖动剧烈变化。否则,网络稍微波动一下,RTO 就变得极小,导致大量不必要的重传;或者 RTO 变得极大,导致真正的丢包迟迟检测不到。

所以,TCP 的做法是:对 RTT 做平滑处理,再在此基础上加上一个“安全余量”。这个安全余量通常用 RTT 的偏差(方差)来衡量。

我记得有一次排查一个跨国业务的延迟问题,发现客户端的 RTO 设成了固定 1 秒。结果跨洋链路的 RTT 在 300ms 到 800ms 之间波动,1 秒的 RTO 在链路好的时候浪费了 200ms 的等待时间,在链路差的时候又不够用,导致频繁超时重传。这就是典型的 RTO 没有跟随 RTT 动态调整的后果。

我的经验:RTO 的设定本质上是一个“平衡艺术”。设得太小,网络稍微抖动就重传,浪费带宽;设得太大,真正的丢包要等很久才能恢复,影响用户体验。好的 RTO 算法,就是在“激进”和“保守”之间找到那个动态平衡点。

2.4 为什么 RTO 不能是固定值?

这个问题,我当年面试的时候被问过。当时我回答得比较浅,后来在项目中踩了坑才真正理解。

原因其实很直接:网络环境是动态的,固定 RTO 无法适应所有场景。

具体来说,有以下几个致命问题:

  1. 网络状况随时在变:白天和晚上的网络延迟可能差 10 倍。你凌晨测出来的 RTT 是 50ms,到了晚高峰可能变成 500ms。固定 RTO 要么在高峰期频繁超时,要么在空闲期白白等待。
  2. 不同链路的 RTT 差异巨大:局域网 RTT 可能只有 1ms,跨洋链路可能 300ms。如果你把 RTO 固定为 200ms,局域网用户觉得太慢,跨洋用户则永远超时。
  3. 网络拥塞会动态变化:当网络出现拥塞时,RTT 会急剧增大。固定 RTO 无法感知这种变化,要么在拥塞时疯狂重传(加剧拥塞),要么在拥塞缓解后仍然用很长的超时时间(浪费恢复时间)。
  4. 移动网络的不稳定性:我做过一个移动端项目,用户在电梯、地铁、隧道等场景下,RTT 可以在 100ms 到 3000ms 之间剧烈跳动。固定 RTO 在这种场景下基本是废的。

我曾经踩过的坑:在一个 IoT 项目中,我图省事把 RTO 写死成了 3 秒。结果设备部署到不同地区后,有的地区 RTT 只有 200ms,3 秒的超时让设备在断线后要等很久才能恢复;有的地区 RTT 高达 2 秒,3 秒又不够用,导致频繁误判丢包。后来改成动态 RTO 算法,问题才解决。

所以,现代 TCP 协议栈都采用动态 RTO 算法。经典的算法是 Jacobson 算法,它通过平滑 RTT(SRTT)和 RTT 偏差(RTTVAR)来计算 RTO:

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

其中 α 通常取 1/8,β 通常取 1/4。这个公式的核心思想是:用历史数据平滑当前 RTT,再用偏差来留出安全余量。偏差越大,RTO 就越大,避免因为 RTT 抖动导致误判。

嗯,这里要注意,这个公式只是基础版本。实际生产环境中,还会加入 Karn 算法(避免重传二义性)、RTO 下限(通常 200ms)和上限(通常 60 秒)等约束。

2.5 知识体系总览

下面这张图,我把 RTT 和 RTO 的核心关系、计算流程以及影响因素画了出来。你可以把它当作本章的知识地图:

RTT 与 RTO 知识体系 RTT(往返时间) RTO(重传超时) 动态计算 影响 RTT 的因素 物理距离 网络拥塞 路由跳数 中间设备处理 RTO 计算流程(Jacobson 算法) 测量原始 RTT (每次 ACK 到达) 计算平滑 SRTT SRTT = (1-α)×SRTT + α×RTT 计算偏差 RTTVAR RTTVAR = (1-β)×RTTVAR + β×|SRTT-RTT| 计算最终 RTO RTO = SRTT + 4 × RTTVAR 关键:RTO 必须动态调整,固定值无法适应网络变化。α=1/8,β=1/4 是经典取值。

从这张图你可以看到,RTT 是基础输入,经过平滑和偏差计算后,最终输出 RTO。整个过程是闭环的——每次 ACK 到达都会更新 RTT,进而影响下一次的 RTO 决策。