4、网络延迟排查:RTT测量方法、延迟抖动分析、路径MTU探测

网络延迟,说白了就是数据包在两端之间跑一趟的时间。很多TCP重传问题,根因并不在协议栈本身,而是网络路径太慢、太抖,或者MTU不匹配。我排查过不少案例,最后发现是中间某个路由器把大包静默丢了,客户端还傻傻地等ACK。

这一章,我们聚焦三个核心点:怎么测RTT才准、延迟抖动怎么量化、以及MTU探测到底怎么做。嗯,都是实战里天天要用的东西。

核心观点:延迟问题不解决,TCP重传永远查不干净。RTT、抖动、MTU,这三者是网络质量的“三驾马车”。

4.1 RTT测量方法

RTT(Round-Trip Time)是TCP最基础的延迟指标。但很多人测RTT的方法不对,拿到的数据根本没用。

4.1.1 传统方法:ping

ping用的是ICMP,不是TCP。它只能给你一个“网络层”的往返时间。但TCP的RTT是端到端的,中间还有NAT、防火墙、负载均衡器。我遇到过好几次,ping延迟只有1ms,但TCP连接就是慢。为什么?因为防火墙对ICMP放行,但对TCP数据包做了深度检测,加了延迟。

所以,我的建议是:不要只用ping来评估TCP性能。它只能作为参考,不能作为决策依据。

4.1.2 更准的方法:tcpdump + 抓包分析

要测真实的TCP RTT,最靠谱的办法是抓包。我们看三次握手的时间戳,或者数据包与对应ACK的时间差。

具体怎么做?

  1. 在客户端和服务端同时抓包(或者在一端抓,只要能看清双向流量)。
  2. 找到SYN包和SYN-ACK包,计算时间差。这是“握手RTT”。
  3. 找到数据包和对应的ACK包,计算时间差。这是“数据RTT”。

用tcpdump命令示例:

# 在客户端抓包,过滤目标端口80
tcpdump -i eth0 -nn 'tcp port 80' -w rtt_capture.pcap

# 然后用Wireshark打开,或者用tshark分析
tshark -r rtt_capture.pcap -T fields -e tcp.analysis.ack_rtt

我个人习惯用Wireshark的“TCP Stream Graph”里的“Round Trip Time Graph”。它能直观地看到RTT随时间的变化曲线。有一次我帮客户排查,发现RTT每隔几秒就跳变一次,最后定位到是中间链路的负载均衡策略在周期性切换路径。

小技巧:抓包时尽量在两端同时抓,时间要同步(用NTP)。这样能区分延迟是发生在正向路径还是反向路径。

4.1.3 内核视角:ss 与 tcpprobe

如果你不想抓包,也可以用Linux内核暴露的接口。ss命令可以看当前连接的RTT:

ss -ti | grep -A 1 'ESTAB'

输出里会有 rtt:10.5/0.5 这样的字段。前者是平滑后的RTT,后者是抖动(mdev)。

更细粒度的,可以用 tcpprobe 模块。它能把每个ACK的RTT采样值打印到内核日志里。我在做性能调优时经常用它,比抓包轻量得多。

4.2 延迟抖动分析

RTT的平均值重要,但抖动(Jitter)更重要。为什么?因为TCP的RTO(重传超时)计算依赖于RTT的方差。抖动越大,RTO就越容易被误触发,导致不必要的重传。

4.2.1 抖动的定义

简单说,抖动就是RTT的变化幅度。标准定义是相邻两个RTT样本的绝对差值的平均值。但实际中,我更关注“突发抖动”——短时间内RTT从1ms跳到100ms又跳回来。

4.2.2 怎么量化抖动

用Wireshark打开抓包文件,看“TCP Analysis Flags”里的“ACK RTT”。导出这些数据,用Excel或Python算标准差。我一般看两个指标:

指标 含义 健康阈值
RTT均值 平均往返时间 < 50ms(局域网)
< 200ms(广域网)
RTT标准差 抖动幅度 < 10ms(稳定链路)
< 30ms(可接受)
最大RTT 峰值延迟 不应超过均值的3倍

4.2.3 实战案例:抖动的根因

我曾经排查过一个视频卡顿问题。RTT均值只有20ms,但标准差高达40ms。抓包发现,每过10秒左右,RTT会突然跳到200ms,持续2秒后恢复。最后定位到是出口路由器在做QoS队列调度,视频流量被降级到了低优先级队列,和后台备份流量抢带宽。

解决方案很简单:给视频流量打DSCP标记,走高优先级队列。抖动立刻降到了5ms以下。

注意:不要只看RTT平均值。平均值掩盖了抖动。一定要看分布,看P99、P999分位值。很多“慢”问题,其实是抖动造成的。

4.3 路径MTU探测

MTU(Maximum Transmission Unit)是链路层能承载的最大数据包大小。以太网通常是1500字节。但路径上可能有隧道(GRE、IPsec、MPLS),这些隧道会减小有效MTU。

如果TCP发送了一个超过路径MTU的包,中间路由器会把它分片,或者直接丢弃并返回ICMP“需要分片但DF标志置位”的消息。但很多网络设备会过滤ICMP,导致发送端永远收不到这个信号,包就静默丢了。这就是经典的“PMTUD黑洞”问题。

4.3.1 手动探测MTU

最直接的办法:用ping命令,设置DF标志,逐步调整包大小。

# Linux/macOS
ping -M do -s 1472 -c 3 目标IP
# -M do: 禁止分片
# -s 1472: 负载大小(1472 + 28字节IP+ICMP头 = 1500)

# 如果通,增大到1473,看是否通
ping -M do -s 1473 -c 3 目标IP

找到能通过的最大包大小,加上28就是路径MTU。我习惯从1500开始往下减,每次减10,快速定位范围,再精确到1字节。

4.3.2 用traceroute探测每跳MTU

有些时候,我们需要知道路径上哪一跳限制了MTU。可以用带MTU探测功能的traceroute:

# Linux
traceroute --mtu 目标IP

它会发送不同大小的包,并报告哪一跳返回了“Fragmentation Needed”消息。嗯,前提是中间设备没有屏蔽ICMP。

4.3.3 TCP层面的MSS协商

TCP在三次握手时,会通过MSS(Maximum Segment Size)选项协商最大段大小。MSS = MTU - IP头 - TCP头。通常以太网MSS是1460(1500 - 20 - 20)。

但要注意:如果中间路径的MTU小于1500,而客户端和服务端都通告了1460的MSS,那大包就会被丢弃。我曾经在云环境里遇到过,VPC内部MTU是1400,但虚拟机网卡默认MTU是1500,导致所有超过1400的TCP包都被静默丢弃,应用层表现为“连接建立成功,但发数据就卡死”。

排查建议:在两端抓包,看三次握手的MSS值。如果MSS大于路径MTU-40,就要手动调整网卡MTU,或者在TCP层设置MSS钳制。

4.3.4 如何解决PMTUD黑洞

如果确认中间设备屏蔽了ICMP,有几种办法:

  • 手动设置MSS:在防火墙或路由器上,对TCP流量钳制MSS。比如设置为1400,确保所有包都能通过。
  • 调整网卡MTU:在服务器上把eth0的MTU改为1400。但要注意,这会影响所有流量,包括非TCP的。
  • 启用TCP MSS clamping:很多Linux发行版默认启用了。检查一下:sysctl net.ipv4.tcp_mtu_probing。设为1或2可以启用内核级的MTU探测。
# 启用TCP MTU探测
sysctl -w net.ipv4.tcp_mtu_probing=1
# 1: 禁用黑洞检测,但启用MTU探测
# 2: 启用黑洞检测和MTU探测

我个人建议在生产环境开启 tcp_mtu_probing=2。它能让TCP在发现包丢失时,主动尝试更小的MSS,绕过ICMP黑洞。

网络延迟排查知识体系 RTT测量方法 延迟抖动分析 路径MTU探测 ping (ICMP) tcpdump + Wireshark ss / tcpprobe RTT标准差 P99 / P999分位值 QoS队列调度影响 ping -M do 探测 traceroute --mtu MSS协商与钳制 核心原则 不要只看平均值 · 抓包是金标准 · 警惕ICMP黑洞

这张图把本章的三个核心模块串起来了。RTT测量是基础,抖动分析是进阶,MTU探测是常见陷阱。三者结合,才能全面掌握网络延迟的排查能力。

嗯,最后提醒一句:工具只是手段,理解原理才是根本。下次遇到“慢”的问题,别急着怀疑代码,先看看网络路径是不是在“捣乱”。