重传机制全景:RTO、FR、SACK、DSACK、TLP

TCP 的重传机制,说白了就是网络出问题时的「后悔药」。数据包丢了怎么办?发出去没回应怎么办?TCP 靠一套组合拳来兜底。我刚开始调优时,总觉得重传就是超时重传那么简单,后来被线上问题狠狠教育了一顿——嗯,今天咱们把这五种机制一次讲透。

核心观点:五种重传机制不是替代关系,而是互补关系。它们覆盖了从「丢包检测」到「精准恢复」的全路径。

1. 超时重传(RTO)—— 最后的保底

RTO 是最原始、最保守的重传方式。发送方发出一个数据段后,启动一个定时器。如果定时器到期还没收到 ACK,就认为这个段丢了,直接重传。

RTO 的值不是固定的。TCP 会动态计算 RTT(往返时间),然后根据 RTT 的波动来调整 RTO。公式大致是:

RTO = SRTT + max(G, 4 × RTTVAR)

其中 SRTT 是平滑后的 RTT,RTTVAR 是 RTT 的偏差,G 是时钟粒度。说白了,RTO 要比当前 RTT 大一些,留出余量。

我曾经踩过的坑:有一次线上服务延迟飙升,排查发现 RTO 被配置成了固定值 1 秒。结果网络抖动时,TCP 死等 1 秒才重传,用户体验直接崩了。记住:RTO 必须动态计算,别手贱写死。

RTO 的缺点很明显:等待时间太长。尤其是在移动网络或跨洲链路中,RTT 本身就不小,再等一个 RTO,延迟直接翻倍。所以 TCP 又引入了更快的检测手段。

2. 快速重传(FR)—— 靠重复 ACK 触发

快速重传的思路很简单:接收方收到乱序包时,会重复发送期望的序列号。发送方收到 3 个重复的 ACK 后,不等超时,直接重传。

举个例子:

  • 发送方发了 Seq=1, 2, 3, 4, 5
  • 接收方收到了 1, 3, 4, 5,但 2 丢了
  • 接收方会连续发 ACK=2(期望收到 2)
  • 发送方收到 3 次 ACK=2 后,立即重传 Seq=2

快速重传比 RTO 快得多。它不需要等定时器,靠 ACK 的「重复信号」就能判断丢包。我在项目中见过不少场景,快速重传把恢复时间从秒级降到了毫秒级。

一个小细节:快速重传的阈值是 3 次重复 ACK。为什么是 3?因为 1 次可能是乱序,2 次也可能是乱序,3 次基本可以确认是丢了。这是经验值,Linux 内核里写死的。

3. 选择性确认(SACK)—— 精准定位丢失

快速重传有个问题:它只知道某个包丢了,但不知道后面哪些包也丢了。比如丢了 Seq=2,但 Seq=3,4,5 都到了。快速重传只会重传 Seq=2,然后等 ACK 推进。如果后面还有丢包,还得再等下一轮。

SACK 解决了这个问题。接收方在 ACK 里带上 SACK 选项,告诉发送方:「我收到了哪些区间,哪些还没收到」。发送方一看就明白了,可以一次性重传所有丢失的段。

SACK 的格式大致如下:

TCP 选项:SACK
  第一个块:左边界 = 1000,右边界 = 2000
  第二个块:左边界 = 3000,右边界 = 4000

这意味着接收方收到了 1000-2000 和 3000-4000,中间 2000-3000 丢了。发送方直接重传这个区间就行。

我个人的习惯:只要条件允许,我一定开启 SACK。它在高丢包率场景下效果极好,能减少 50% 以上的无效重传。Linux 默认就开了,但有些嵌入式系统或老设备会关掉,记得检查。

4. 重复 SACK(DSACK)—— 揪出重复传输

DSACK 是 SACK 的增强版。它告诉发送方:「你发的某个包,我收到了不止一次」。说白了,就是报告重复传输。

DSACK 有什么用?两个场景:

  • 检测虚假重传:如果发送方误判了丢包,重传了一个其实已经到达的段,DSACK 能暴露这个问题。
  • 帮助调整 RTO:如果 RTO 设置得太激进,导致频繁虚假重传,DSACK 的数据可以用来校准 RTO 参数。

我记得有一次调优,发现重传率很高,但实际丢包率很低。打开 DSACK 一看,原来是中间设备乱序导致发送方误判。调整了接收窗口和乱序阈值后,问题就解决了。

注意:DSACK 需要发送方和接收方都支持。如果有一方不支持,这个选项就白搭。建议在服务器端强制开启,客户端一般默认也支持。

5. 尾部丢失探测(TLP)—— 提前发现尾部丢包

TLP 是相对较新的机制,主要解决一个特殊场景:数据流快结束时,尾部包丢了怎么办?

举个例子:你发了 100 个包,最后一个包丢了。接收方不会发重复 ACK(因为没有后续包触发),只能等 RTO 超时。这个等待时间可能很长。

TLP 的做法是:在发送完一批数据后,如果在一定时间内没有收到任何 ACK,就主动发一个「探测包」。这个探测包可以是新的数据,也可以是重传的最后一个包。如果探测包触发了 ACK,发送方就知道网络还活着,继续等待;如果没触发,就启动 RTO。

TLP 的典型流程:

  1. 发送完数据后,启动 TLP 定时器(通常比 RTO 短)
  2. 定时器到期,发送一个探测包
  3. 收到 ACK → 继续正常传输
  4. 没收到 ACK → 进入 RTO 重传
我建议:TLP 在短连接场景下效果特别明显。比如 HTTP 请求,数据量小,尾部丢包概率高。开启 TLP 后,尾部延迟可以从 200ms 降到 20ms 左右。

五种机制的关系图

下面这张图展示了五种重传机制的触发条件和协作关系:

TCP 重传机制全景图 TCP 重传机制全景图 发送方 接收方 数据段 ACK / SACK 超时重传 (RTO) 定时器到期触发 快速重传 (FR) 3次重复ACK触发 选择性确认 (SACK) 精准报告接收区间 重复SACK (DSACK) 检测重复传输 尾部丢失探测 (TLP) 主动探测尾部丢包 💡 协作关系说明: • RTO 是最后防线,其他机制失效时兜底 • FR + SACK 组合是最常用的快速恢复方案 • DSACK 和 TLP 是增强补充,解决特定场景问题

五种机制的对比

机制 触发条件 恢复速度 适用场景
RTO 定时器到期 慢(秒级) 所有丢包场景(最后保底)
FR 3次重复ACK 快(毫秒级) 单个丢包,后续有数据到达
SACK 与FR配合使用 快 多个丢包,需要精准恢复
DSACK 收到重复段 诊断用途 检测虚假重传、调整参数
TLP 尾部无ACK 中等(比RTO快) 短连接、尾部丢包
实战建议:在 Linux 服务器上,可以用 ss -i 查看当前连接的 SACK 和 TLP 状态。如果发现 SACK 没开启,检查 net.ipv4.tcp_sack 参数。TLP 在 3.x 以上内核默认开启,老内核需要手动配置。

五种机制各有分工。RTO 是兜底,FR 是快速响应,SACK 是精准定位,DSACK 是诊断工具,TLP 是尾部补漏。你想想看,如果没有这套组合拳,TCP 在网络差的时候得有多狼狈?