1. 重传基础:TCP重传机制概述
TCP 重传,说白了就是「丢了重发」。但怎么知道丢了?什么时候重发?发多少?这里面的门道,我做了十几年网络优化,踩过的坑真不少。
先看一张总览图,把今天要讲的几个核心机制串起来。
1.1 超时重传(RTO)
超时重传是最原始、最朴素的机制。发送方发了一个数据包,然后启动一个定时器。如果定时器到期还没收到 ACK,就认为包丢了,重新发送。
这里有个关键问题:超时时间设多长?
设短了,网络稍微抖动一下就误判丢包,大量重传把带宽吃光。设长了,真丢包时恢复太慢,用户感知到卡顿。
我早年调优过一个跨国专线项目,RTT 在 200ms 到 800ms 之间剧烈波动。默认的 RTO 算法根本扛不住,丢包恢复要等 1 秒多。后来我手动调整了 RTO 的上下限,才把重传延迟压到 400ms 以内。
Linux 内核里,RTO 的计算遵循 RFC 6298:
SRTT = (1 - α) × SRTT + α × RTT // 平滑 RTT
RTTVAR = (1 - β) × RTTVAR + β × |SRTT - RTT| // RTT 偏差
RTO = SRTT + max(G, 4 × RTTVAR) // 最终超时时间
α 通常取 1/8,β 取 1/4。G 是时钟粒度,Linux 下一般是 1ms 或 10ms。
我的经验:RTO 最小值默认 200ms,最大值 120s。但在数据中心内部,RTT 通常不到 1ms,200ms 的 RTO 下限太保守了。我建议把 tcp_rto_min 调到 10ms 甚至更低,能显著提升内网重传恢复速度。
1.2 快速重传
超时重传有个硬伤——太慢。你想想看,如果 RTO 是 200ms,那丢包后至少等 200ms 才能恢复。对于实时性要求高的场景,这不可接受。
快速重传就是来救场的。它的原理很简单:
- 接收方收到乱序包时,会立即回复一个 ACK,确认序号是期望收到的那个
- 发送方收到 3 个相同的重复 ACK,就认为包丢了,不等超时直接重传
举个例子:发送方发了 Seq=1,2,3,4,5。包 2 丢了,接收方收到 3、4、5 时,每个都会回复 ACK=2。发送方连续收到 3 个 ACK=2,就知道包 2 丢了,立刻重传。
注意:快速重传依赖「重复 ACK」的数量阈值。Linux 默认是 3,可以通过 tcp_reordering 调整。我在一个无线网络项目中遇到过,由于信道波动导致少量乱序,误触发了快速重传。后来把阈值调到 4,误判率降了 60%。
1.3 SACK(选择性确认)
快速重传有个问题:它只知道「某个包丢了」,但不知道「哪些包已经收到了」。所以重传时只能从丢失的包开始,把后面的全部重传一遍——这里面可能有很多是接收方已经收到的,白白浪费带宽。
SACK 就是来解决这个问题的。它在 TCP 选项字段里携带一个「位图」,告诉发送方:哪些段收到了,哪些没收到。
| 特性 | 无 SACK | 有 SACK |
|---|---|---|
| 重传粒度 | 从丢失点开始全部重传 | 只重传真正丢失的段 |
| 带宽浪费 | 高(可能重传已收到的数据) | 低(精确重传) |
| 适用场景 | 少量丢包 | 批量丢包、乱序严重 |
| Linux 默认 | 关闭(需开启 tcp_sack) | 开启 |
我记得有一次排查一个 CDN 节点的性能问题,发现回源带宽利用率只有 60%。打开 tcp_sack 后,重传量直接降了 40%,带宽利用率升到 85% 以上。
SACK 的局限:SACK 选项最多携带 4 个块(每个块描述一段连续已收数据)。如果丢包非常分散,超过 4 个块就无法完整描述了。不过在实际网络中,4 个块基本够用。
1.4 DSACK(重复 SACK)
DSACK 是 SACK 的增强版。它解决的是另一个问题:发送方重传了一个包,但这个包接收方其实已经收到了。
为什么会发生这种情况?
- ACK 丢失:接收方发了 ACK,但发送方没收到,于是超时重传
- 路径延迟:重传的包和原始包在网络上「擦肩而过」,接收方先收到重传包,后收到原始包
- 误判:网络乱序导致快速重传误触发
DSACK 在 SACK 的第一个块中报告「重复收到的数据范围」。发送方看到 DSACK 后,就知道自己做了无用功,可以调整重传策略。
排查技巧:用 tcpdump 抓包时,如果看到 DSACK 频繁出现,说明网络存在严重的 ACK 丢失或路径不对称。我曾经在一个卫星链路项目中,DSACK 占比高达 15%,后来通过增大接收窗口和调整 RTO 下限,把 DSACK 降到了 2% 以下。
1.5 四种机制对比
| 机制 | 触发条件 | 重传时机 | 重传粒度 | 主要开销 |
|---|---|---|---|---|
| 超时重传 | RTO 超时 | 慢(200ms+) | 从丢失点开始全部 | 等待时间 |
| 快速重传 | 3 个重复 ACK | 快(毫秒级) | 从丢失点开始全部 | 可能误判 |
| SACK | 快速重传 + SACK 选项 | 快 | 只重传丢失的段 | 选项头部开销 |
| DSACK | 收到重复数据 | 报告已发生 | 不触发重传,仅报告 | 额外 ACK 处理 |
实际生产环境中,这四种机制是协同工作的。超时重传是兜底,快速重传是主力,SACK 做精确控制,DSACK 做反馈修正。你想想看,没有 SACK 的时候,一次批量丢包可能引发几十次不必要的重传,带宽和 CPU 都扛不住。
嗯,这一章的基础概念就讲到这里。后面我们会深入每个机制的源码实现、参数调优和故障定位方法。到时候我会拿出更多实际案例,包括我在某电商大促期间解决的重传风暴问题——那次差点把整个机房的带宽打满。