排除法确认问题链路:用同一根网线把笔记本接到WAN口,在不改任何配置的前提下测速。如果笔记本直连正常,问题就在路由器内网侧;如果笔记本也不正常,那就把运营商光猫也拉进来排查。这一步能帮你砍掉一半的排查路径。
我当时实测下来,问题出在路由器的一个隐蔽设置上:WAN口的MTU值被设成了1480,而不是标准的1500。这个MTU指的是"单个数据包最多能装多少字节",如果WAN侧封装带了额外头部(比如PPPoE拨号),确实需要调小。但这台路由器是DHCP上网,不拨号,1480这个值就导致所有超过该尺寸的报文在WAN口被丢弃。视频连接建立时的握手包小,能通过;一旦进入大数据量传输,超过MTU的包直接失踪,TCP左等右等等不到确认,只能一遍遍超时重传,表现在用户侧就是"能连上,但过一会儿就断"。
这背后牵出的就是 TCP超时重传机制。它属于TCP协议栈最基础也最容易被忽略的部分,正常情况下它默默运转,你根本感觉不到;一旦网络里出现丢包、延迟抖动、乱序,它的表现就直接决定了你的连接是"卡一下恢复"还是"断线重连"。这个机制不只是一个简单的"超时了就重发",它背后涉及定时器管理、RTO动态计算、指数退避、快速重传、选择性确认等一系列环环相扣的设计,任何一个环节的参数不合理,都会让传输效率大打折扣。
这篇文章我把TCP超时重传机制从原理到实践完整拆一遍。适合几类人看:刚入门网络、想在tcpdump抓包里看懂"为什么这里有重传"的学生;在工作中排查"网速慢""连接不稳定"的运维和开发;还有做网络调优、想深入理解TCP状态机的工程师。
2. 超时重传到底在解决什么问题:重新认识"可靠传输"的成本
TCP号称可靠传输协议,这是相对于UDP而言的。但"可靠"不是免费的,它建立在"确认-重传"这套机制之上:发送方发出一个报文段,必须收到接收方的ACK确认,才认为数据真的到了;如果一段时间内没收到ACK,发送方就认为报文丢了,重新发一遍。
听起来很简单,但这句话里藏着一个要命的细节:"一段时间内"是多久? 这个时间叫RTO(Retransmission Timeout,重传超时时间)。它不能是拍脑袋定的固定值,因为网络状况是动态变化的——局域网内RTT可能只有0.5毫秒,跨洋链路的RTT可能有200毫秒,移动网络在信号差的时候RTT能飙到好几秒。如果RTO设短了,报文明明还在路上,只是慢了点,发送方就急着重传,结果网络里塞满重复数据;如果RTO设长了,报文真丢了,发送方却傻等,对端和应用干着急。
这个矛盾就是超时重传机制的核心矛盾。要理解它,先要理解TCP干了哪些事:
- 发送方发送一个段,启动重传定时器。定时器到期前收到ACK,说明数据安全到达,定时器取消。
- 定时器到期还没收到ACK,发送方假定数据丢失,重传该段,并重置定时器。此时RTO会翻倍(指数退避),因为网络可能已经拥塞,重传要更谨慎。
- 如果累计收到3个重复ACK,发送方认定数据丢失,不等定时器到期就立即重传。这就是快速重传,后面专门讲。
你发现没有,超时重传的本质是"以时间为代价换取可靠性"——每次重传都意味着等了一个RTO,而这个时间通常比一个RTT大得多。为什么不能每次都靠快速重传解决?因为快速重传依赖接收方收到后续数据才能触发重复ACK,如果后面没有数据了(比如最后一个段丢了),或者接收方的ACK也丢了,TCP只能靠超时兜底。所以超时重传是TCP可靠性的最后一道保险,谁都能失效,它不能失效。
理解这层逻辑之后,你再去看线上问题就会通透很多:所有"连接突然卡住""隔几秒掉线"的现象,十有八九背后都在反复发生超时重传。重传本身不是错误,它是TCP的自我修复;但短期内频繁重传,说明网络路径上存在丢包、拥塞或者设备配置有误,这才是需要关注的信号。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
3. RTO的计算:为什么说这是TCP里最精巧的估计算法
前面说了RTO不能是固定值,那它到底怎么定?TCP的答案是:采样当前网络RTT,基于历史统计动态估算。这里面涉及的核心算法是Karn算法和Jacobson算法,网上讲TCP/IP的书都会提,但很多书只讲公式不讲为什么,导致看完还是一头雾水。
3.1 先搞清楚RTT怎么测量
RTT是数据从发送到收到ACK的往返时间。测量它最简单的办法:发一个段,记下时间T1,收到该段的ACK时记下时间T2,RTT = T2 - T1。看着简单,实际有两个坑:
- 重传段的RTT不能用来更新样本。假设一个段超时重传了,你收到一个ACK,你怎么知道这个ACK对应的是第一次发送的那个段,还是重传的那个?如果对应的是重传的,你却按第一次发送的时间来计算RTT,算出来的值就会虚大。Karn算法的核心贡献就是:发生重传时,当前这次的RTT样本不用于更新RTO,并且RTO直接翻倍(指数退避),等收到非重传段的确认后再恢复采样。这个"重传不算数"的原则非常实用,它避免了错误样本污染估算结果。
- 延迟确认机制会干扰RTT采样。接收方为了减少ACK包数量,可能等200ms才统一确认多个段。这样测到的RTT里混入了接收方故意拖延的时间,不是纯粹的链路耗时。所以新版内核在采样时会尝试区分是延迟确认还是真实RTT,这是细节中的细节。
3.2 RTO估算公式的演进
经典RFC 2988定义的算法是:
code复制SRTT = (1 - α) × SRTT + α × RTT // 平滑RTT,α=0.125
RTTVAR = (1 - β) × RTTVAR + β × |SRTT - RTT| // RTT偏差,β=0.25
RTO = SRTT + 4 × RTTVAR
这组公式看着唬人,拆开看其实很朴素:
- SRTT是加权平均,对新老样本各给一定权重,让估算值平滑变化。α取0.125,意味着新样本只占1/8的权重,历史样本占7/8。为什么新样本权重这么低?因为网络RTT有抖动,如果让新样本权重太高,RTO会被个别异常值带偏。
- RTTVAR是"平均偏差",它是用SRTT和当前RTT的差来度量波动。乘以4是为了留足余量,相当于在平均往返时间基础上加上4倍波动幅度。
为什么要用"平均值+4倍波动"而不是直接取历史最大值?原因很简单:历史最大值是过去式,网络状况是动态的,取最大值会导致RTO整体偏大,丢包发生后要傻等太久;取平均值又不够保险,一旦RTT突然变大,按平均值设置的RTO会频繁触发不必要的重传。4倍这个系数是经验值,权衡了"误判丢包"和"等待过久"两种风险。
3.3 实测里RTO的初始值和下限
在Linux上,一个新建TCP连接的初始RTO是多少?你可能会猜一个漂亮的整数,实际是1秒。这个值来自RFC 6298,再往下还有一个200ms的下限,RTO最小不能小于200ms。为什么有下限?因为如果RTO设得太小,报文轻微延迟就会触发重传,而重传又会加剧拥塞,形成恶性循环。所以即使你的局域网RTT只有0.2ms,RTO也不可能是0.2ms,最低200ms。
这个知识点对排查问题特别关键。我见过有人用ethool看网卡统计,发现重传计数在涨,立刻断定网络丢包。其实未必,如果应用发送数据间隔很小,而接收方的ACK因为延迟确认机制或者CPU调度慢了一点,就可能触发超时重传。这时候看RTT,才0.1ms,但TCP的RTO下限是200ms,只要ACK在200ms内没回来,发送方就会误判丢包。所以200ms这个下限在某些极端场景下本身就是一种"人为丢包"。如果你在做高频交易或者工业控制这类对延迟极度敏感的业务,可以考虑调整这个参数(内核编译选项或sysctl)——但这是高危操作,生产环境要谨慎再谨慎。
我自己的习惯是,在排查重传问题时先跑一下抓包,看RTO的实际值是多少。比如一个RTT只有5ms的内网连接,如果RTO稳定在200ms,说明内核已经把RTO压到下限了;如果RTO在500ms以上,那说明曾经有过高RTT样本污染了估算(比如某次GC停顿或CPU飙高导致ACK迟了),可以通过清空连接或修改参数解决。
4. 重传策略的演进与取舍:从固定超时到快速重传和SACK
超时重传不是TCP唯一的修复手段,甚至算不上最高效的手段。前面几轮TCP版本演进,核心就是在"怎么更快发现丢包、怎么减少不必要的重传"上做文章。把这几个机制放在一起对比,你才能理解为什么现代TCP的可靠性策略如此立体。
4.1 超时重传:不得已而为之的兜底手段
超时重传触发条件是RTO到期还没收到ACK。它的优点是通用——任何丢包场景都能兜住,不需要接收方配合;缺点是反应太慢——假设RTO是200ms,一个报文丢了,发送方得等200ms才重发,这段时间发送方干不了别的(如果是停止等待协议),或者阻塞发送窗口(如果窗口内的数据全指望这个ACK推进)。
更糟糕的是,超时重传会连带触发拥塞控制。Linux上发生超时重传后,拥塞窗口直接降到初始值,然后重新慢启动。这意味着一次超时不仅损失了等待时间,还把之前的发送节奏全部打乱,吞吐量断崖式下跌。所以网络工程师看TCP重传统计时,超时重传(tcp_timeouts)是最让人紧张的指标——它意味着那次传输经历了完整的"等待+退避+重启动"过程。
4.2 快速重传:用重复ACK提前感知丢包
快速重传解决的问题是"能不能不等超时,更早发现丢包"。它的原理是利用接收方的"累计确认+重复ACK"行为:
- 接收方收到乱序的段时,会回复一个ACK,确认号指向期望收到的连续序号。
- 假如发送方发了序号1、2、3、4、5,其中2丢了。接收方收到1后确认1;收到3时发现不连续,还是确认1(重复ACK);收到4、5时同样确认1。于是发送方在短时间内收到3个"确认1"的重复ACK。
3个重复ACK为什么能推断丢包?因为如果只是报文乱序,后续的重排包到达后累计ACK马上就会推进;而连续3个重复ACK说明接收方至少看到了3个后续段都没法等来2号段,从概率上讲,链路真的把2弄丢的可能性远大于网速慢。所以发送方不等RTO到期,直接重传2号段。这个机制在Linux内核里默认开启,效果是显著缩短了丢包修复时间——RTO可能是200ms甚至1秒,而3个重复ACK在RTT内就能集齐,通常是几十毫秒就触发重传。
4.3 SACK和DSACK:从"整块重传"到"精确补传"
快速重传出现之后,又有一个新问题:发送方只知道2丢了,但它不知道自己后面发的3、4、5是否都到了。传统TCP是累计确认,如果只丢了一个段,发送方可以只重传2;但如果丢了多个段(比如2和4都丢了),发送方重传2之后,收到的ACK可能确认的还是"2"之前的位置,于是还得再重传3(虽然3已经收到了),这就是"Go-Back-N"式的整块重传,浪费带宽。
SACK(Selective Acknowledgment,选择性确认)改变的是接收方的行为:它可以在ACK里额外声明"我收到了哪些不连续的段",发送方拿到这份清单后,只重传真正丢失的段。这个机制的优化效果在高带宽高延迟(BDP大)的链路上尤其明显,比如跨洋传输,一次丢两个段,没有SACK可能要重传整个窗口的数据,有了SACK就精准补两个段。
DSACK则在SACK基础上进一步:接收方告诉发送方"你重传的段,我之前其实已经收到了"。这能帮发送方识别出"是不是自己的重传太激进了",避免反复重传同一个段。在乱序比较严重的移动网络环境下,DSACK能有效抑制拥塞窗口的误判收缩。
4.4 什么情况下快速重传和SACK都失效
这些机制再精巧,也有边界。最常见的情况是**"尾包丢失"**——发送方把所有数据发完,最后一个段丢了,接收方没有后续数据可接收,不会触发重复ACK,发送方只能等RTO超时。这时候快速重传帮不上忙,SACK也没有信息可报告,兜底的还是超时重传。
另一个是ACK丢失。接收方确实收到了数据,也发了ACK,但ACK被网络丢弃。发送方没等到ACK,触发超时重传,接收方收到重复数据后发现自己已经确认过了,于是再次回复ACK。这个过程看着无害,但重传的数据白白消耗了带宽。如果链路本身已经很拥塞,这会雪上加霜。
讲到这里你应该感觉到了:现代TCP的可靠性不是靠某一个机制单打独斗,而是一套多层防御体系。超时重传是最后防线,快速重传是主力先锋,SACK是精确制导。理解它们各自的适用场景和失效边界,是排查网络问题的基础。
5. 从内核到线上:超时重传相关参数、排查手段和实战经验
前面把原理讲透了,这块聊点实操。超时重传机制不是固化在协议里的死规矩,内核提供了很多可调参数,线上问题的排查也有固定套路。这部分内容是我在日常运维中反复用到的,分享出来。
5.1 关键内核参数速查
Linux下与超时重传最相关的参数是通过sysctl或/proc/sys/net/ipv4/ 下的文件配置的,我列几个最常用的:
| 参数 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
tcp_retries1 |
3 | 在重传达到该次数后,内核会通知上层(比如应用层)网络可能有问题,但TCP不会立即放弃,继续重传 | 一般不用动 |
tcp_retries2 |
15 | TCP在重传达到该次数后放弃连接,通知应用层连接断开。这个值直接决定了断线需要多久 | 有状态防火墙环境可能要调低,避免连接被中间设备清理 |
tcp_retries1和tcp_retries2之间的间隔指数退避 |
- | 每次重传RTO翻倍,从200ms或初始RTO开始 | 可以通过ss -o看当前连接的RTO |
tcp_syn_retries |
6 | SYN握手阶段的重传次数 | 内网直连可以调小,跨公网建议保持 |
这里有个容易误解的地方:tcp_retries2看起来是"重传次数",但重传间隔是指数增长的,所以15次重传不是15乘以200ms那么简单。从初始RTO=1s开始,经历15轮指数退避,总耗时可长达约15分钟。也就是说,一个TCP连接在"看起来还活着"的状态下,可能需要15分钟才被内核判定为死亡。这解释了为什么很多应用层没有心跳机制的长连接,会在真断网后很久才报错。
5.2 抓包定位超时重传的完整思路
排查超时重传,我推荐用tcpdump抓包配合Wireshark的图形化分析。具体步骤:
第一步:抓包拿到重传证据。 在客户端或服务器端执行抓包,注意抓包位置要放在怀疑丢包的两端之间。如果你连服务器A上的应用都正常,但客户端B连过去有问题,比较可靠的办法是在A和B分别抓包,然后对比。只在一端抓包,你看到的"重传"可能是对端已经收到、只是ACK丢了,也可能真的没收到,没法下结论。
抓包命令参考:
bash复制tcpdump -i eth0 -s 96 -w tcp_retrans.pcap 'tcp port 8080'
抓一段时间或者复现问题后,用Wireshark打开pcap文件,在"分析"菜单里选择"专家信息",Wireshark会标记出所有重传、快速重传、重复ACK、乱序等事件。从这里你能得到几个关键信息:重传的是哪些序号、重传间隔是否固定、有没有伴随重复ACK。
第二步:判断是否真丢包。 如果一端持续重传但从未收到SACK或重复ACK,大概率是ACK在返回路径上被丢弃,或者接收方压根没收到数据。此时检查两端之间路径上的防火墙、路由器是否丢弃了特定大小的报文——用ping测不同payload大小,比如:
bash复制ping -s 1472 -M do 对端IP # 1500 - 28 = 1472,测标准MTU
ping -s 1400 -M do 对端IP # 测小包
如果大包不通、小包通,基本就是MTU问题。顺着这条线查路径上所有设备的MTU配置,很快能定位。
第三步:观察RTO的变化规律。 在Wireshark里给TCP报文加一列"RTO"(发送方的重传超时),看它是不是按指数规律递增。如果RTO已经增长到几秒还不停,说明这不是瞬时抖动的丢包,而是持续性的链路问题;如果RTO始终稳定在最小值附近,可能是发送方的RTO下限或网络抖动导致的误判。
5.3 结合真实案例:一个"30秒一卡"的疑难杂症
前面讲原理和参数,下面用一个我实际处理过的案例把整个链路串起来,你会更容易理解这些机制是怎么协同工作的。
场景是国内某机房到海外机房的专线,延迟约180ms,业务方反馈"每30秒固定卡一下,持续大概5秒,然后恢复"。抓包发现TCP重传恰好以30秒为周期出现,每次重传后拥塞窗口归零重新爬坡,吞吐量从峰值跌到谷底,过几秒才缓过来。第一反应是"链路丢包",但检查了专线提供商提供的丢包率,只有0.01%,低得离谱。
继续深挖抓包,发现重传的段都是大小接近1460字节的大包,而小包没有重传。结合之前ping测试的结果——1472字节的包通过但1480的包不通——基本确定路径上存在一个MTU为1500的上游设备,但该设备不会正确处理DF标志位(Don't Fragment,禁止分片),导致大包被直接丢弃。为什么是30秒的周期?因为应用每30秒会触发一次大数据量传输,比如缓存回源或日志上报,平时的小请求不涉及大包,自然不会触发重传。
解决方案是在服务器网卡上把MTU从1500调到1400,确保所有发出的大包都小于路径MTU。改完后重传立刻消失,卡顿问题随之解决。
这个案例里,超时重传机制扮演了"信号灯"的角色:网络里丢包本身是隐藏的,但TCP的重传行为把"这里有问题"明晃晃地标记出来了。你不需要网络设备支持什么特殊协议来报告丢包,只要在端侧抓一次包,从重传规律就能逆向推断出问题所在——这正是掌握TCP机制的最大价值。
6. 关于超时重传,最后想说的几个教训
做了这么多年网络排障,我对超时重传机制最大的感受是:它既是协议栈的兜底设计,也是网络问题的放大镜。没有它,一次简单的丢包就会让应用层陷入永久等待;有了它,丢包虽然会被修复,但伴随着吞吐量下降、时延增加,如果你是应用的调用方,体验到的就是"卡"和"慢"。
所以我给刚入行的朋友一个建议:排查网络性能问题,先别急着怀疑网络设备、带宽、防火墙,先抓个包看看有没有重传。有重传,说明TCP正在替你修复问题,你要做的是找到它在修什么问题;没有重传但依然慢,那才轮到带宽、延迟、应用处理能力这些因素。
另外提醒一句,重传不等于丢包。有时候对端机器负载飙高,CPU忙于处理其他中断,ACK发出晚了,发送方也会误判为丢失并重传。这时候抓包看到的是"重传+重复ACK",但实际链路质量很好。处理方式不是去折腾链路,而是优化对端的处理性能。
最后分享一个我踩过的坑:有段时间我在生产环境折腾TCP参数,把tcp_retries2从15调低到5,想着断线检能快一点。结果业务高峰期网络抖动,大量长连接在5次重传后就被强制断开,应用层来不及重连,造成了比原来更严重的故障。后来我才意识到,重传次数和超时时间不是越小越好,它们是吞吐和可靠性的平衡。调这些参数,一定要结合业务的实际容忍度来做,而且要经过灰度验证。
关于超时重传,我该讲的、能踩的坑都在这了。与其背下那些冷冰冰的公式和参数,不如自己搭个环境丢包模拟一把,比如用tc netem制造延迟和丢包,再用tcpdump观察重传行为的变化。那种"亲手把网络搞坏再修好"的体验,比任何文章都来得深刻。
