4. 快速重传:重复ACK触发机制,与超时重传的对比,实际抓包分析
说到TCP重传,很多人第一反应就是超时重传。但说实话,等超时再重传,效率太低了。你想想看,一个数据包丢了,发送方要傻等一个RTO(重传超时时间)才能发现,这期间网络可能早就空闲了。
快速重传就是为了解决这个问题而生的。它不需要等超时,而是靠接收方发回的重复ACK来触发。我个人觉得,这是TCP协议里最巧妙的设计之一。
4.1 重复ACK是怎么产生的
先看一个典型场景。发送方发了三个包:Seq=1、Seq=2、Seq=3。接收方只收到了Seq=1和Seq=3,Seq=2丢了。
接收方的行为是这样的:
- 收到Seq=1,正常回复ACK=2(期望下一个收到Seq=2)
- 收到Seq=3,但Seq=2还没到,回复ACK=2(重复ACK)
- 后续再收到任何包,只要Seq=2没到,就一直回复ACK=2
这就是重复ACK的由来。说白了,接收方在说:「我一直在等你发Seq=2,别发后面的了,先把丢的那个补上!」
关键点:重复ACK不是乱发,它只重复确认「接收方期望收到的下一个序列号」。收到3个重复ACK(包括原始ACK共4个),发送方就认为发生了丢包。
4.2 快速重传 vs 超时重传
这两个机制的区别,我整理了一张对比表。我在项目里经常拿这个表给团队新人讲,效果不错。
| 对比维度 | 快速重传 | 超时重传 |
|---|---|---|
| 触发条件 | 收到3个重复ACK | RTO计时器到期 |
| 检测速度 | 快,约1个RTT内 | 慢,至少1个RTO(通常200ms+) |
| 拥塞控制 | 触发快速恢复(cwnd减半) | 触发慢启动(cwnd=1) |
| 适用场景 | 单个包丢失,后续包成功到达 | 严重拥塞或链路中断 |
| 对吞吐的影响 | 较小,能快速恢复 | 较大,可能造成空闲期 |
这里有个坑,我踩过。有一次线上服务延迟飙升,我一看监控,超时重传次数暴涨。但抓包一看,其实只是个别包丢了,快速重传没触发。为什么?因为接收方收到的后续包太少,凑不够3个重复ACK。这种情况,快速重传就帮不上忙了。
注意:快速重传依赖后续包的到达来产生重复ACK。如果丢包后发送方停止发送,或者接收方缓存满了,重复ACK就不会出现。这时候只能靠超时重传来兜底。
4.3 实际抓包分析
光讲理论不过瘾,咱们直接看抓包。下面是我从一次真实故障中截取的片段。
# 抓包片段:快速重传过程
1 0.000000 192.168.1.1 → 192.168.1.2 TCP Seq=1000, Len=1460
2 0.000100 192.168.1.2 → 192.168.1.1 TCP ACK=2460
3 0.000200 192.168.1.1 → 192.168.1.2 TCP Seq=2460, Len=1460 ← 这个包丢了
4 0.000300 192.168.1.1 → 192.168.1.2 TCP Seq=3920, Len=1460
5 0.000400 192.168.1.2 → 192.168.1.1 TCP ACK=2460 ← 第1个重复ACK
6 0.000500 192.168.1.1 → 192.168.1.2 TCP Seq=5380, Len=1460
7 0.000600 192.168.1.2 → 192.168.1.1 TCP ACK=2460 ← 第2个重复ACK
8 0.000700 192.168.1.1 → 192.168.1.2 TCP Seq=6840, Len=1460
9 0.000800 192.168.1.2 → 192.168.1.1 TCP ACK=2460 ← 第3个重复ACK
10 0.000900 192.168.1.1 → 192.168.1.2 TCP Seq=2460, Len=1460 ← 快速重传!
看第3行,Seq=2460的包丢了。但发送方不知道,继续发后面的包。接收方收到Seq=3920、5380、6840后,连续回了3个ACK=2460。发送方在第10行立即重传了Seq=2460。整个过程只用了0.9毫秒,比等超时快太多了。
抓包技巧:用Wireshark过滤快速重传,可以直接用 tcp.analysis.fast_retransmission。这个过滤器会标出所有被判定为快速重传的包。我排查问题时,习惯先看这个过滤结果,再结合 tcp.analysis.retransmission 看超时重传,两个一对比,问题基本就清楚了。
4.4 快速重传的局限性
快速重传不是万能的。我遇到过一种情况:发送方发了10个包,只有第一个丢了,后面9个都到了。按理说能触发快速重传。但问题是,接收方的接收窗口满了,后面的包被丢弃了。结果重复ACK没发出来,最后还是超时重传。
另外,快速重传对乱序包也很敏感。如果网络本身就有乱序,接收方可能会误判为丢包,触发不必要的快速重传。嗯,这里要注意,乱序和丢包在接收方看来,表现是一样的——都是「期望的包没到」。区别在于,乱序的包稍后会到,而丢包的不会。
我曾经在一条高延迟链路上遇到过这个问题。链路本身有轻微的乱序,结果快速重传频繁触发,吞吐量反而下降了。后来我们调整了重复ACK阈值,从3改到5,才缓解了这个问题。
4.5 知识体系图
下面这张图总结了快速重传的核心逻辑和与超时重传的关系。我建议你把它记在脑子里,排查问题时能快速定位。
4.6 优化建议
最后,结合我的实战经验,给你几条优化建议:
- 监控重复ACK比例:如果重复ACK占比超过1%,说明网络丢包率偏高,需要排查链路质量。
- 调整重复ACK阈值:默认是3,但在高乱序网络中,可以适当调高到4或5。Linux下通过
sysctl net.ipv4.tcp_reordering调整。 - 开启SACK:选择性确认能让接收方精确告诉发送方丢了哪些包,避免不必要的重传。我建议所有场景都开启。
- 注意接收窗口:如果接收窗口太小,后续包可能被丢弃,导致重复ACK无法产生。适当增大接收缓冲区能缓解这个问题。
快速重传和超时重传,一个快一个稳,配合起来才能保证TCP的可靠性。你在实际排查中,可以先用快速重传定位丢包,再结合超时重传判断拥塞程度。两者结合,问题定位会快很多。