尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

TCP重传间隔不翻倍?从RTO原理到Wireshark抓包排查全解析

TCP重传间隔不翻倍?从RTO原理到Wireshark抓包排查全解析 碰到这个抓包现象我印象挺深。那次是帮一个项目组排查跨机房数据传输慢的问题同事在服务器上抓了两分钟的包拉我过去看一脸困惑地说“你看这重传第一次超时重传间隔0.2秒第二次变0.4秒第三次居然没翻倍反而变成0.3秒了。是不是协议栈出bug了”我看了看package告诉他Wireshark里标着重传的不代表都是同一个重传机制触发出来的。你用“不翻倍”这个现象去推结论得先把重传的类型分清楚。这篇文章就把这类问题彻底捋一遍从RTO的算法原理到Wireshark里怎么看、怎么过滤、怎么区分“超时重传”和“快速重传”再给一个完整的排查案例看完你也能自己判断到底是网络故障还是协议栈异常。1. 场景回顾一次抓包发现了“不翻倍”的重传1.1 抓包现场与现象描述当时的网络环境很简单A机房的业务服务往B机房的数据库服务器传一批数据应用层用的是MySQL协议底下走TCP。用户反馈某张表的批量导入偶尔会卡一下持续时间不长但一天能出现好几次。为了定位在业务服务器上起了Wireshark其实生产环境我一般用tcpdump抓完再导进Wireshark抓了大概两分钟的包。打开抓包文件先用过滤表达式tcp.analysis.retransmission把所有重传包筛出来时间列Seconds Since Previous Displayed Packet逐个看过去序列大概是这样的包号Time包类型间隔(s)15210.000000TCP Retransmission-16230.204813TCP Retransmission0.20481317760.416734TCP Retransmission0.21192118920.445120TCP Retransmission0.02838620110.731205TCP Retransmission0.286085看到第4次重传和第3次重传之间只差了0.028秒同事的第一反应就是这不对RTO超时重传应该是2倍、4倍、8倍这样指数往上翻的怎么会突然冒出一个间隔特别短的而且后面又回到0.28秒是不是我们改了内核参数拥塞控制算法出问题了这里所有人对TCP重传机制的理解几乎都会被教科书上的“指数退避”四个字带偏。教科书说RTO超时后下个RTO翻倍比如初始0.2秒超时后0.4秒再超时0.8秒一直到64秒封顶。这是指数退避的简化模型但教科书忽略了一个前提这个规律只适用于“同一个包的连续超时重传”。一旦中间有其他机制介入比如快速重传、部分确认、SACK、窗口探测那么时间间隔立刻就会失去“翻倍”的规律。1.2 为什么“RTO翻倍”被当成了铁律RTO翻倍这个机制在RFC 6298的5.5节里有明确描述当一个包超时重传后新的RTO取旧RTO的两倍作为下次重传的等待时间。这么设计的原因不复杂如果网络真的拥塞了你重传得越频繁拥塞只会越严重所以间隔放大是给网络降温的退避策略。这个策略在拥塞控制里叫指数退避和以太网的CSMA/CD退避算法思路一脉相承。但注意RFC里还有个细节翻倍是针对“同一个包”的。如果后面又有新的RTT样本进来SRTT和RTTVAR都在重新计算这时候RTO本身会动态变化翻倍规则会发生错位。更要命的是Wireshark列出来的TCP Retransmission和TCP Fast Retransmission在Info列长得不一样但如果你过滤条件统一只写tcp.analysis.retransmission这两类包会被一起筛出来混在一起看时间间隔就会产生“超时时间不翻倍”的错觉。快速重传的间隔取决于接收方重复ACK到达的时间差这个时间和RTO完全没有关系自然也就不存在翻倍。所以看到“不翻倍”第一个要做的判断是这些重传到底是定时器到期触发的超时重传还是收到3个重复ACK触发的快速重传。2. TCP超时重传公式拆解RTO到底怎么算的2.1 RTT测量与平滑想真正理解“翻倍”什么时候生效得先把RTO的算法摊开看。RTO的全称是Retransmission Timeout超时重传定时器的触发时间。它不是一个固定值而是根据网络往返时间RTT动态计算出来的。内核会为每个TCP连接维护两个变量SRTTSmoothed Round Trip Time平滑后的RTT和RTTVARRound Trip Time VariationRTT的方差估计。每次收到一个新的ACK只要这个ACK确认的是之前没确认过的数据就计算出一个RTT样本然后更新两个变量SRTT (1 - α) × SRTT α × RTT RTTVAR (1 - β) × RTTVAR β × |SRTT - RTT|其中α通常取1/8β取1/4。这个公式本质上是一个加权移动平均新的SRTT主要由历史值决定最新的RTT样本只占1/8的权重目的是防止某一次网络的突发抖动把RTO拉得剧烈变化。RTTVAR的更新稍稍反直觉它用的是新的RTT样本和更新后的SRTT之间的差这个差值越大说明网络抖动越大RTO就越要留出余量。最终RTO按下面的公式计算RTO SRTT max(G, 4 × RTTVAR)G是时钟粒度Linux下一般是1毫秒HZ1000或者10毫秒取这个max的目的是防止RTTVAR很小的时候RTO算出来比一个时钟tick还短。RTO下限在Linux有个内核参数tcp_rto_min默认是40毫秒HZ1000时或者200毫秒HZ250时就算RTT只有0.1毫秒RTO也不会低于这个下限。2.2 从RFC 6298看RTO更新链路RFC 6298把RTO管理分成了几个阶段连接建立时的初始RTO。RFC建议设为1秒RFC 6298后来建议可以是1秒Linux实现里初始SYN重传间隔在1秒左右但SYN重传用的是另一套参数tcp_syn_retries控制次数不是这里要展开的重点。每次收到有效的RTT样本后按公式更新RTO。连接刚建立、还没有足够RTT样本的那段时间协议实现可能直接用初始值也可能用SYN阶段的计时数据。某条数据超时后立即进入指数退避RTO翻倍。翻倍的同时拥塞窗口cwnd会被重置为1个MSS慢启动重新开始这是和RTO翻倍配套的另一个动作。接收方如果回的是重复ACK且重复ACK达到3个TCP不会等RTO超时直接触发快速重传。TCP的RTO更新有一条非常关键的规则叫Karn算法重传过的包如果收到确认不能用这个确认的RTT样本去更新SRTT和RTTVAR。原因很直接你重传后收到的ACK可能是对第一次发送的包的确认也可能对的是重传包的确认你根本分不清如果用这个时间去计算RTT算出来的值会严重偏小导致RTO估计不足引发更多无谓的重传。Karn算法不光是搞科研的RFC条款它在Wireshark分析里有个很实际的反映如果一个包发生过超时重传后面即使网络变好RTO也不会立刻被修正因为重传后的ACK不参与RTT计算RTO会一直按退避后的较大值往下走直到出现一次没有重传的干净数据交换RTO才会重新校准。这也是为什么有些抓包里会看到连续几次超时间隔却并不是严格的2倍递增——某一次退避增长之后来了一个正常数据包RTO重新计算并收缩下一次超时间隔就变短了。2.3 指数退避的触发条件指数退避不是所有重传都触发它只针对RTO定时器到期那一种情况。你从Wireshark的Expert Info里看到红色感叹号标记里面分三类TCP Retransmission超时重传对应RTO定时器到期。TCP Fast Retransmission快速重传对应收到3个或更多重复ACK。TCP Spurious Retransmission虚假重传通常是对端其实已经收到数据但ACK丢失或迟到导致发送方重复发了一遍。RTO超时重传会触发指数退避快速重传不会因为快速重传根本没有等定时器它是在RTO到期之前就被重复ACK提前激活的。快速重传之后发送方一般进入快速恢复Fast Recovery流程。Linux的快速恢复用的是PRR算法Proportional Rate Reduction这个算法的作用是控制重传后拥塞窗口的恢复速度它和RTO翻倍之间没有直接的数理关系。另外有一种情况是重传定时器已经设了但定时器还没到期时收到了部分ACKPartial ACK也就是说发送方重传的数据没被完全确认但接收方确实确认了一部分新数据。这种情况下TCP会重新启动定时器计时用的RTO值还是当前已经过退避调整的RTO值不会继续翻倍。这就解释了一些抓包里两个超时重传之间的间隔看起来是“1倍”而不是“2倍”的现象。3. 重传不翻倍的几种典型原因3.1 快速重传不等于超时重传最常见的不翻倍场景就是快速重传混进来了。看Wireshark的Info列如果显示的是TCP Fast Retransmission它的时间间隔由什么决定假设发送方连续发了包1到包10其中包3在网络里丢了。接收方收到包4到包10后因为TCP累积确认的特性没办法确认新数据只能每收到一个包就回一个重复ACK重复ACK的序列号指向包3。包4触发第一个重复ACK包5触发第二个包6触发第三个这三个重复ACK到达发送方的时间取决于包4、5、6的到达时间。如果包4和包5间隔0.2毫秒包5和包6间隔0.3毫秒那么发送方收到第3个重复ACK并触发快速重传时距它发出包3的时间就是0.5毫秒左右。这个时间间隔完全取决于局部网络时延和RTO毫无关系自然也不会翻倍。有心的读者到这里应该能想到快速重传对网络丢包的响应非常敏锐一个RTT内就能发现并重传。所以当网络里发生轻微丢包时Wireshark里率先看到的往往是快速重传。只有网络情况非常糟糕丢包程度严重到接收方连重复ACK都发不回来或者ACK链路本身也丢包严重快速重传的触发条件凑不齐发送方才会等到RTO超时。3.2 部分确认与乱序缓冲第二种典型场景和乱序有关。TCP接收方有乱序缓冲能力包不按顺序到达时接收方先把后到的数据缓存起来然后继续发重复ACK等缺失的包补上后再一并确认。这个过程如果拖得太久发送方的重传定时器会先到期触发超时重传。但就在定时器到期的前一瞬间接收方又发来了一个ACK确认了乱序缓冲里的一部分数据发送方看到这个ACK会认为链路还有活性定时器被重置RTO计算采用当前的SRTT和RTTVAR。这个场景在Wireshark里的表现是Info列显示TCP Retransmission但紧接着的ACK包确认号变大了而且后面没有再出现连续的超时重传。如果你只看重传包的时间间隔会觉得这次超时后紧接着又超时了一次间隔还特别短实际上中间夹着ACK重置了定时器。还有一种更容易误判的接收方启用了SACKSelective Acknowledgment选择性确认它对乱序的数据给出了精确的反馈发送方可以根据SACK块判断哪些包丢了。SACK出现后发送方重传的策略会更精准可能只重传丢失的包不会傻等RTO。这种情况下如果Wireshark里看到重传间隔不规律基本可以断定是SACK在起作用。SACK相关的选项在Wireshark的TCP协议详情里都能看到。展开TCP头看Options区域里面有SACK_PERM、SACK块的范围信息比如SACK range: 10001-20000这里的数字范围告诉你是哪一段数据被接收方缓存了。3.3 零窗口与窗口探测第三种场景牵扯到TCP流控。假设接收方应用程序读数据太慢接收缓冲区满了接收方会通告Window 0这时候发送方不能继续发数据但会定期发送窗口探测包TCP Zero Window Probe。窗口探测包本身也算是一次传输如果探测包丢了发送方也会触发重传但这时候的重传涉及的是探测包不是业务数据TCP对待它的方式和数据包超时重传略有不同。Linux里窗口探测的间隔由tcp_probe_interval这类参数控制它有自己的节奏不和RTO的指数退避完全绑定。在Wireshark里遇到TCP ZeroWindow和TCP ZeroWindowProbe标记时要意识到这个连接其实已经处于“半停摆”状态。重传时间不翻倍可能是因为根本没在进行正常的数据传输定时器计算方式已经切换到探测模式了。我见过不少同学在这个场景下研究半天RTO算法方向完全错了。3.4 数据包重组带来的“伪重传”第三种容易被忽略的原因是抓包位置导致的“伪重传”。现在主流的网卡和服务器的TCP卸载引擎TSO、GSO会把上层传下来的一大段数据切分成多个MSS大小的包再发出去。如果在发送方本机抓包Wireshark看到的是TCP层传下来的大段数据而实际网卡发出的可能是多个小包。当Wireshark把TSO切分出来的重复数据误判成重传时就会出现间隔极短、完全不符合退避规律的重传记录。标准做法是在对端抓包或者把tcp.analysis.retransmission和不带TSO的抓包文件对比。如果本机抓包显示“重传”但对端抓包对应位置只有一个包到达那就说明是TSO/GSO导致的伪重传网络里实际并没有发生重传。Wireshark菜单里有Statistics → TCP Stream Graphs配合序列号曲线看伪重传的序列号并不会出现回退而是连续上升很容易区分。4. Wireshark实操如何准确识别“不翻倍”重传4.1 过滤与时间差计算定位“不翻倍”问题Wireshark里几个过滤表达式要熟tcp.analysis.retransmission tcp.analysis.fast_retransmission tcp.analysis.out_of_order tcp.analysis.duplicate_ack tcp.analysis.zero_window tcp.analysis.spurious_retransmission只写tcp.analysis.retransmission会把超时重传和快速重传都筛出来。想分开看用tcp.analysis.retransmission and !tcp.analysis.fast_retransmission筛出纯超时重传用tcp.analysis.fast_retransmission看快速重传。时间显示默认是“从抓包开始到当前包的时间”对分析重传间隔不友好。我一般会改成Seconds Since Previous Displayed Packet在菜单View → Time Display Format里选。这样过滤后时间列显示的数值就是相邻两个重传包之间的时间间隔一眼就能看出是否翻倍。如果要更精确地分析一个连接的RTO变化推荐用Statistics → TCP Stream Graphs → Time-Sequence Graph (Stevens)或tcptrace的时序图。序列号曲线里出现“平台期”就是发送方在等ACK平台期的长度近似对应RTO。把平台期的长度量出来两两对比翻倍的规律就清楚了。4.2 Expert Info信息解读Wireshark底部的Expert Info可以快速汇总整个抓包文件里的异常事件打开Expert Info对话框菜单Analyze → Expert Info。看“TCP”这个分组重传、快速重传、乱序、重复ACK都列在里面。注意“Notes”里写的触发条件Wireshark对每个重传包都会写明是基于定时器还是基于重复ACK。比如一条Note可能是“This frame is a (suspected) retransmission”“suspected”这个词很重要因为Wireshark是通过启发式规则判断的不一定100%准确。它会对比当前包的序列号和之前出过的包如果序列号相同但包大小不同可能因为TSO被拆包就存在误判。所以专家信息只用来快速定位问题范围真正下结论还得靠逐包分析。我自己的习惯是用Expert Info先扫一遍看有没有特殊的Event类型比如zero window、window update、dup ack flood这些它们能帮助快速判断重传的根因是拥塞还是丢包还是接收端处理不过来。4.3 抓包位置与时序分析想在“不翻倍”这类问题上拿到决定性证据抓包位置比过滤表达式更重要。建议在发送方和接收方同时抓包。发送方的包用于确认数据是否真的发出来了接收方的包用于确认数据是否真的收到了两边一对比才能判断是网络丢了包还是发送方自己出了问题。如果是中间链路丢包你分别在A端和B端抓包A端能看到发送和重传B端只能看到第一次到达的数据和后续的重传到达这样就能排除很多干扰。抓包文件里的时间戳精度也要检查。Wireshark的抓包文件时间戳精度取决于采集工具Wireshark默认是微秒级精度tcpdump在大多数平台也是微秒级。如果发现时间戳是毫秒级甚至秒级那分析“不翻倍”就毫无意义了因为误差已经大于你关注的间隔。可以在Statistics → Capture File Properties里查看时间戳精度。5. 一个完整案例的排查复盘5.1 网络环境与抓包位置回到开头说的那个案例。业务服务器A到数据库服务器B中间经过两层交换机和一个防火墙链路带宽1GbpsRTT约0.2毫秒正常时传输速度非常快。出问题的时间段集中在每天上午10点左右持续几分钟。我在两台机器上同时抓包A端命令用的是tcpdump抓完了再导入Wireshark分析。tcpdump -i eth0 -s 96 -w /tmp/a_tcpdump.pcap host B的IP and tcp port 3306-s 96限制每个包只抓前96字节对分析TCP头足够了。生产环境流量大抓全包会把pcap文件撑爆限制长度是基本操作。5.2 逐包分析过程打开抓包文件先过一遍Expert Info发现大量TCP Retransmission和少量TCP Fast Retransmission同时还有TCP DUP ACK。用tcp.analysis.fast_retransmission过滤快速重传的包集中在几个时间点间隔几十毫秒一次。再用tcp.analysis.retransmission and !tcp.analysis.fast_retransmission过滤超时重传时间序列拉开看间隔在0.2秒到0.4秒之间波动确实没有严格的2倍关系。继续看序列号曲线。在Statistics → TCP Stream Graphs → Time-Sequence Graph (Stevens)里选一个出问题的连接发现曲线在快速重传发生的地方出现了一个小回退随后迅速恢复在超时重传发生的地方曲线出现了一个短暂的平台平台长度约0.2秒左右。这说明超时重传的RTO确实在0.2秒附近波动。到这里已经可以确认RTO没有“严格翻倍”是因为中间频繁插入快速重传每次快速重传之后的RTO校准都重新调整了SRTT和RTTVAR。这不是协议栈bug是链路丢包率偏高导致的。进一步看丢包率。Wireshark里直接用Statistics → TCP Stream Graphs → Throughput看吞吐曲线出问题的时段吞吐出现了明显的锯齿状跌落从接近线速跌到几十Mbps又恢复。配合交换机端口计数发现接入交换机某个端口有大量CRC错误包顺着查下去是光纤接口的收发模块光衰过大导致物理层误码TCP层表现为偶发丢包。5.3 结论与后续优化根因找到后把光纤模块换了光衰恢复正常CRC错误消失抓包确认重传数量大幅下降传输时间恢复稳定。业务侧没有任何代码改动。这个案例的教训是当Wireshark里出现“超时重传时间不翻倍”时不要急着质疑内核实现先分清三类情况。如果重传里混了快速重传说明网络里存在轻微丢包TCP快速重传机制工作正常。如果纯超时重传但间隔不严格翻倍可能是中间有正常ACK重置了定时器或者RTT样本更新导致RTO被重新校准。如果不是内网而是公网传输还有一个常见因素TCP时间戳选项在Linux默认开启对端回的时间戳可以帮助计算RTT但也可能让RTO计算基于错误的RTT样本导致间隔不规律。可以用sysctl net.ipv4.tcp_timestamps临时关闭对比测试但这属于后手排查手段不要一上来就动内核参数。6. 排查经验与个人体会做了这么多年网络排查我总结出几条针对重传分析的通用经验写在这里供参考。第一条不要在Wireshark信息列里直接判断重传类型。信息列显示的“Retransmission”只是一个启发式标记要判断到底是哪类触发得结合上下文。超过两个重复ACK之后出现的重传基本都是快速重传只有一个孤立的重传包前面没有任何重复ACK那大概率是超时重传。Wireshark的Expert Info对话框里会列出触发原因但这个信息藏在底层很多初学者不看看了的人又容易盲信其实还是要自己验证一遍。第二条抓包前搞清楚你的抓包点能“看到什么”。在发送方本机抓包你会看到TSO/GSO产生的伪重传在交换机上做端口镜像抓包你会看到被镜像的重传包但可能看不到另一端的重复ACK用Wireshark自带工具的抓包网卡设置的混杂模式和offload选项都会影响结果。如果条件允许在两端抓包再对比是最稳妥的方案。第三条优先看RTT和重传原因再看时间间隔。很多人上来就研究“为什么不翻倍”实际上时间间隔只是表象真正要回答的问题是为什么这个包丢了为什么重传机制没有更快把它找回来。RTO翻倍的防御意义在于防止网络拥塞如果网络根本不拥塞只是物理层有偶发误码那快速重传和RTO校准会交替出现“不翻倍”反而是TCP适应网络变化的正常表现。第四条也是我今天最想说的一点抓包分析要建立“证据链”。单个包的时间戳、序列号、ACK号、SACK选项这些是零散证据把它们串联起来形成“什么时候丢包、什么时候重传、什么时候恢复”的完整故事才能下结论。Wireshark里的Sequence号、相对时间、Expert Info都是工具工具是拿来辅助推理的不是拿来直接出结论的。最后分享一个小技巧。分析重传问题的时候我习惯先用Statistics → Endpoints把多IP、多会话的流量归组选定一条问题连接后再做TCP Stream Graph。这样做的好处是避免你被不属于该连接的重传包干扰尤其是在DNS轮询或者多活负载均衡的场景里同一个目的IP可能对应多个TCP连接混在一起看时间间隔完全是一团乱麻。先把会话锁定再做时间差分析逻辑上就清晰得多。
返回列表