3. 超时重传(RTO):RTO的计算原理,指数退避算法,重传定时器管理
超时重传,说白了就是TCP的“最后一道防线”。
快速重传靠的是重复ACK来触发,但万一网络里ACK丢了,或者对端真的没收到数据呢?这时候就得靠RTO(Retransmission Timeout)来兜底。我刚开始调TCP栈的时候,总觉得RTO就是个固定超时,后来被线上问题狠狠教育了一顿——固定超时在弱网环境下要么频繁误判,要么死等不重传。
3.1 RTO的计算原理
RTO不能设得太短,也不能太长。太短会导致大量不必要的重传,浪费带宽;太长则让应用层感知到明显的卡顿。那到底怎么算才合理?
核心思路是:根据网络当前的RTT(往返时间)动态调整。TCP维护两个关键变量:
- SRTT(平滑后的RTT):对实测RTT做加权平均,过滤掉突发抖动。
- RTTVAR(RTT偏差):衡量RTT的波动程度。
标准算法(RFC 6298)的计算步骤如下:
- 首次测量:拿到第一个RTT样本后,SRTT = RTT,RTTVAR = RTT / 2。
- 后续更新:每收到一个ACK,更新SRTT和RTTVAR。
其中R'是本次实测的RTT值。RTTVAR = (3/4) * RTTVAR + (1/4) * |SRTT - R'| SRTT = (7/8) * SRTT + (1/8) * R' - 计算RTO:
G是时钟粒度(比如Linux下通常是1ms或10ms)。RTO = SRTT + max(G, 4 * RTTVAR)
关键点:RTO的下限通常是200ms(RFC 6298建议),上限是60秒。低于下限的RTO会被强制钳位到200ms,防止在局域网等低延迟场景下过于激进。
我在项目中遇到过一个问题:某个内网服务延迟只有0.5ms,但RTO被算出来只有几毫秒,结果网络稍微抖动一下就疯狂重传。后来查了内核代码才发现,Linux把RTO_MIN设成了TCP_RTO_MIN(200ms),这才避免了误伤。
3.2 指数退避算法
重传一次没收到ACK,怎么办?再等同样的时间?不行。如果网络真的拥堵了,你越急它越堵。
指数退避的思路很简单:每次重传后,把RTO翻倍。
| 重传次数 | RTO值(初始200ms) | 说明 |
|---|---|---|
| 第1次 | 200ms | 正常超时 |
| 第2次 | 400ms | 翻倍 |
| 第3次 | 800ms | 再翻倍 |
| 第4次 | 1.6s | 继续翻倍 |
| 第5次 | 3.2s | 直到上限60s |
为什么要这么做?你想想看,如果网络已经拥塞了,你还在拼命发数据,只会让拥塞更严重。指数退避相当于给网络一个“喘息”的机会。等退避到一定程度,网络可能已经恢复了,这时候再重传成功率更高。
避坑指南:我曾经调试过一个视频流服务,发现RTO退避到60秒后,用户端直接卡死。原因是应用层没有做超时兜底,TCP重传次数耗尽后连接断开,但应用层还在傻等。后来我们在应用层加了一个5秒的心跳检测,配合TCP的keepalive,才解决了这个问题。
3.3 重传定时器管理
定时器管理是RTO落地的关键。TCP为每个连接维护一个重传定时器,它的状态机其实不复杂:
定时器的核心操作只有三个:
- 启动:发送数据包时,如果定时器没在跑,就启动它,超时时间设为当前RTO。
- 停止:收到ACK确认所有数据都已到达,停止定时器。
- 重启:收到部分ACK(比如选择性确认SACK),但还有数据在途,则重启定时器。
注意:不要每次收到ACK都重启定时器。只有确认了新的数据被对端接收时,才需要重置。否则,如果网络延迟突然变大,频繁重启会导致RTO永远无法触发,这就是所谓的“定时器饿死”问题。
我记得有一次排查一个下载慢的问题,发现客户端RTO定时器一直在重启,但实际数据根本没传过去。原因是服务端发送了窗口更新,客户端误以为有新数据到达,就把定时器重置了。结果就是:数据丢了,但定时器永远不超时,客户端一直傻等。后来我们在代码里加了一个判断——只有确认新数据序号增长时,才重置定时器。
3.4 实践中的调优建议
RTO的默认参数在大多数场景下够用,但如果你在做实时性要求高的应用(比如游戏、视频会议),可以考虑以下调整:
| 场景 | 建议调整 | 风险 |
|---|---|---|
| 低延迟内网 | 降低RTO_MIN到50ms | 外网抖动时可能误重传 |
| 高丢包无线网络 | 增加重传次数上限(默认15次) | 连接断开变慢 |
| 实时音视频 | 关闭指数退避,使用固定RTO | 可能加剧网络拥塞 |
我的习惯:在Linux上,我一般通过sysctl调整net.ipv4.tcp_rto_min和net.ipv4.tcp_retries2。但注意,这些是全局参数,会影响所有连接。如果只想针对特定连接,可以用setsockopt设置TCP_NODELAY和TCP_QUICKACK,间接影响RTO行为。
嗯,RTO这块其实还有很多细节,比如RTT的采样时机(Karn算法)、重传后的RTO计算(避免歧义性ACK)。但掌握了上面这些,你已经能应对90%的RTO相关问题了。剩下的,等遇到具体坑的时候再深挖也不迟。