3、经典RTO计算方法:Jacobson算法、Karn算法、定时器粒度问题、初始RTO的选择
RTO(Retransmission Timeout)怎么算?这问题我早年刚做TCP协议栈时也头疼过。你想想看,网络环境千变万化,RTT(Round-Trip Time)忽高忽低,要是RTO设得太小,没等ACK回来就重传,白白浪费带宽;设得太大,丢包了要等半天才反应,用户体验直线下降。
说白了,RTO计算就是要在「激进重传」和「保守等待」之间找个平衡点。TCP协议栈里最经典的几套算法,我一个个拆开讲。
3.1 Jacobson算法:RTO计算的基石
Jacobson算法是1988年由Van Jacobson提出的,至今仍是绝大多数TCP实现的基础。它的核心思路很简单:用加权移动平均来平滑RTT的波动。
算法分两步走:
- 估算平滑RTT(SRTT):每次收到ACK时,用新测量的RTT样本更新SRTT。
- 计算RTO:在SRTT基础上加上一个「安全余量」,这个余量由RTT的偏差决定。
公式长这样:
SRTT = (1 - α) × SRTT + α × RTT_sample
RTTVAR = (1 - β) × RTTVAR + β × |SRTT - RTT_sample|
RTO = SRTT + 4 × RTTVAR
其中α通常取1/8,β取1/4。为什么是4倍偏差?我记得Jacobson在论文里解释过,4倍偏差能覆盖95%以上的正常RTT波动。我在项目中实测过,这个值确实靠谱——既不会太敏感,也不会太迟钝。
关键点:Jacobson算法假设RTT的分布近似正态,4倍标准差能覆盖绝大多数情况。但如果网络抖动特别剧烈(比如卫星链路),这个假设就不太成立了。
3.2 Karn算法:解决重传二义性
Jacobson算法有个致命问题——重传二义性。什么意思?
假设你发了一个包,超时后重传了一次,然后收到了ACK。这个ACK到底是回应原始包的,还是回应重传包的?你根本分不清。如果错误地把这个ACK当作原始包的响应,算出来的RTT会偏大;如果当作重传包的响应,RTT又会偏小。
Karn算法就是来解决这个问题的。它的规则很简单:
- 重传期间不采样RTT:只要发生了重传,就跳过该次RTT测量。
- 退避RTO:每次重传后,RTO指数退避(通常乘以2)。
- 收到ACK后恢复:当收到一个非重传的ACK时,才重新开始RTT采样。
我曾经在一个弱网环境(丢包率5%左右)调试过,如果不加Karn算法,RTO会频繁震荡,重传次数暴增。加上之后,RTO曲线明显平滑了。
避坑指南:Karn算法虽然解决了二义性,但有个副作用——如果网络持续丢包,RTO会一直退避,导致恢复变慢。现代TCP实现通常会在退避到某个阈值后,强制进行一次RTT探测。
3.3 定时器粒度问题
算法再好,也得靠定时器落地。但定时器有个现实问题——粒度。
早期Unix系统的定时器粒度是500ms,这意味着RTO只能取500ms的整数倍。你算出来RTO是1.2秒?对不起,实际定时器只能设1.5秒或1.0秒。这种粗粒度在高速网络下问题很大:
- RTO偏大:比如RTT只有10ms,但RTO最小也得500ms,白白浪费了重传时机。
- 精度丢失:RTT的微小波动完全反映不到RTO上。
现代系统已经好多了。Linux内核的TCP定时器粒度是1ms(高精度定时器),FreeBSD也类似。但即便如此,还是要注意:
| 系统/环境 | 定时器粒度 | 对RTO的影响 |
|---|---|---|
| 早期Unix | 500ms | RTO只能取500ms整数倍,精度极差 |
| Linux 2.6+ | 1ms(高精度) | 基本无影响,RTO可精确到毫秒 |
| 嵌入式系统 | 10ms~100ms | 需要根据粒度调整RTO下限 |
| 虚拟化环境 | 受宿主机影响 | 可能出现定时器漂移,需额外补偿 |
我在一个嵌入式项目里踩过这个坑。系统定时器粒度是50ms,RTO算出来是23ms,结果实际定时器只能设50ms,导致重传比预期晚了27ms。后来我强制设了RTO下限为定时器粒度的2倍,才稳定下来。
注意:定时器粒度越粗,RTO的「地板效应」越明显。如果你的系统定时器精度不够,建议将RTO下限设为定时器粒度的2~3倍,避免频繁超时。
3.4 初始RTO的选择
连接刚建立时,没有任何RTT样本,RTO怎么设?
RFC 6298规定初始RTO为1秒。为什么是1秒?我理解是权衡的结果:
- 太短:比如100ms,如果网络延迟稍大,就会误判为丢包,导致不必要的重传。
- 太长:比如3秒,用户等半天才重传,体验极差。
1秒这个值,在大多数局域网和互联网场景下都能接受。但要注意几个特殊情况:
- 数据中心网络:RTT通常在100μs~1ms,1秒的初始RTO太保守了。很多数据中心TCP实现会把这个值降到200ms甚至更低。
- 卫星链路:RTT可能高达500ms~1秒,1秒的初始RTO又太激进了。这种情况下建议适当调高。
- 移动网络:4G/5G的RTT通常在20ms~100ms,1秒的初始RTO还算合理,但如果有快速重传机制,可以进一步优化。
我的建议:如果你的应用场景比较固定(比如内部数据中心),可以硬编码一个更合适的初始RTO。但如果是通用场景,还是遵循RFC 6298的1秒标准,等第一个RTT样本出来后,算法会自动调整。
3.5 核心流程总结
下面这张图把整个RTO计算流程串起来了:
嗯,到这里RTO计算的核心内容就讲完了。Jacobson算法提供了平滑的RTT估算,Karn算法解决了重传二义性,定时器粒度决定了算法的落地精度,初始RTO则是整个流程的起点。这四个点环环相扣,缺一不可。