3、高级恢复策略:选择性确认(SACK)、重复SACK(DSACK)、前向纠错(FEC)原理
好,咱们继续往下聊。上一章我们讲了基础的超时重传和快速重传,说白了就是「丢了就重发」。但实际网络环境远比这复杂——丢一个包就重传整个窗口?太浪费了。今天要聊的三个高级策略,就是来解决这个问题的。
我个人习惯把这三个技术分成两类:SACK/DSACK 属于「精确反馈」,告诉发送端到底丢了什么;FEC 属于「主动冗余」,提前塞点冗余数据,丢了也不用重传。两者思路完全不同,但目标一致——减少不必要的重传,提升吞吐。
核心观点:没有 SACK 的 TCP,就像蒙着眼睛开车——只知道撞了,不知道撞在哪。SACK 给了你一张「事故现场地图」。
3.1 选择性确认(SACK)—— 精准定位丢失数据
先回忆一下传统 TCP 的确认机制。接收端只能确认「连续收到的最大序号」,比如收到 1、2、3、5、6,它只能 ACK 3,告诉发送端「3 及之前都收到了」。发送端一看,4 没确认,那就重传 4 吧。但 5 和 6 其实也到了,却要等 4 到了才能被确认——这就是所谓的「头部阻塞」。
我在项目中遇到过这样一个场景:一条跨国链路上,RTT 高达 200ms,带宽 100Mbps。没有 SACK 时,只要丢一个包,整个窗口都要等重传,吞吐直接掉到 30Mbps。开了 SACK 之后,吞吐稳定在 85Mbps 以上。差距就这么大。
SACK 的工作原理
SACK 在 TCP 选项字段里塞入一组「非连续已收数据块」的边界。接收端收到乱序包后,除了发正常的 ACK,还会带上 SACK 选项,告诉发送端:
- 我收到了 5-6,但中间缺了 4
- 我收到了 8-10,但中间缺了 7
发送端一看就明白了——只重传缺失的 4 和 7,不用动其他数据。
| 特性 | 传统 ACK | SACK |
|---|---|---|
| 反馈粒度 | 只确认连续数据 | 可确认多个不连续块 |
| 重传范围 | 从丢失点开始全部重传 | 只重传真正丢失的段 |
| 适用场景 | 低丢包率、小窗口 | 高丢包率、大窗口、长肥网络 |
| 选项开销 | 无额外开销 | 每个 ACK 增加 8-40 字节 |
实战建议:现代 Linux 默认开启 SACK(net.ipv4.tcp_sack = 1)。但有些老旧嵌入式设备或专有协议栈可能不支持。我建议你在部署前用 tcpdump 抓包确认一下——看 SYN 包是否携带 SACK Permitted 选项。
3.2 重复 SACK(DSACK)—— 揪出「假丢包」
DSACK 是 SACK 的增强版。它解决了一个很实际的问题:如果重传的数据其实没丢,只是延迟到达了,怎么办?
举个例子。发送端发了包 1、2、3,接收端都收到了,但 ACK 在路上延迟了。发送端等超时后重传包 1,结果重传的包和原始包都到了接收端。接收端收到重复数据,如果不告诉发送端,发送端还以为自己重传对了——实际上这次重传是浪费的。
DSACK 的做法是:接收端在 SACK 选项里报告「我收到了重复的数据块」。发送端收到后就知道——哦,原来这次重传是多余的,网络其实没丢包,只是延迟了。
我曾经排查过一个诡异的问题:某台服务器的重传率高达 5%,但链路质量明明很好。开了 DSACK 抓包分析,发现 80% 的重传都是「假丢包」——数据其实到了,只是 ACK 被对端网卡合并延迟了。调整了中断合并参数后,重传率降到 0.5%。
注意:DSACK 不会触发任何新的重传行为,它只用于信息收集。发送端根据 DSACK 可以调整自己的 RTO 估算和拥塞窗口,但不会因为收到 DSACK 就立即改变发送策略。这个细节很多人搞混。
3.3 前向纠错(FEC)—— 把冗余塞进数据流
SACK 和 DSACK 都是「事后补救」——丢了再重传。FEC 的思路完全不同:发送数据时多塞一些冗余信息,接收端即使丢了一部分,也能自己算出来。
你想想看,这就像寄快递时多放一份说明书——就算包裹在路上被压坏了一角,收件人也能根据说明书自己修复。不需要让寄件人再发一次。
FEC 的两种常见实现
- XOR 奇偶校验(1+1 保护):每 N 个数据包生成 1 个冗余包。丢失任意 1 个包,都可以用其他 N 个包和冗余包 XOR 恢复。
- Reed-Solomon 编码(K+M 保护):K 个数据包生成 M 个冗余包。最多容忍丢失 M 个包,恢复能力更强,但计算开销也更大。
| 方案 | 冗余开销 | 恢复能力 | 计算复杂度 | 典型应用 |
|---|---|---|---|---|
| XOR (1+1) | 1/N | 最多丢 1 个 | 极低 | 实时语音、视频 |
| Reed-Solomon (K+M) | M/K | 最多丢 M 个 | 中等 | 卫星通信、存储系统 |
| RaptorQ | 可调 | 任意丢包模式 | 较高 | 流媒体、大规模分发 |
关键权衡:FEC 不是万能的。冗余多了,有效带宽就少了。丢包率 1% 时加 10% 冗余可能很划算;丢包率 0.1% 时加 10% 冗余就是浪费。我一般建议:丢包率 > 2% 时考虑 FEC,< 0.5% 时优先用 SACK。
3.4 三种策略的协同与选择
实际工程中,这三个策略不是互斥的,而是配合使用的。我画了一张图来说明它们的关系:
从图上可以看得很清楚:FEC 在发送端做「预防」,SACK/DSACK 在接收端做「精准反馈」。两者配合,才能达到最优效果。
实际选型建议
- 实时音视频(WebRTC):丢包率 5-20%,延迟敏感。用 FEC(XOR 或 Reed-Solomon)+ NACK(类似 SACK 的反馈机制)。重传来不及,主要靠 FEC。
- TCP 长肥网络(数据中心互联):丢包率 0.1-1%,带宽大。用 SACK + DSACK,FEC 基本不用。重传成本低,精确反馈更划算。
- 卫星通信:丢包率 1-5%,RTT 高达 500ms+。必须用 FEC,重传一次要等 1 秒,谁也受不了。
- 无线/移动网络:丢包率波动大(0.5-10%)。建议动态切换——低丢包时用 SACK,高丢包时开启 FEC。
我的经验:不要试图在所有场景都用同一套配置。我见过一个团队在丢包率 0.1% 的内网开了 20% 的 FEC 冗余,白白浪费了 20% 带宽。反过来,在卫星链路上只靠 SACK 重传,吞吐惨不忍睹。先测量,再配置——这是铁律。
3.5 小结
今天聊的三个策略,本质上是在回答同一个问题:数据丢了,怎么用最小的代价恢复?
- SACK 告诉你「丢了哪几个」——精准重传,不浪费
- DSACK 告诉你「哪些重传是多余的」——帮你优化参数
- FEC 提前塞冗余——丢了也不用重传