凌晨三点,PagerDuty 响了
你打开监控面板,发现核心 API 的 P99 延迟从平时的 45ms 飙升到了 3800ms。没有 CPU 飙升,没有内存泄漏,没有磁盘 IO 瓶颈——所有常规指标都是绿的。这大概率是一个网络层的问题。
网络故障是生产环境里最阴险的一类问题。它不会像 OOM 那样给你留一个 kill log,也不会像死锁那样在堆栈里摆个 trace。它只给你一堆「超时」「连接拒绝」「connection reset」——然后你的任务是逆向还原出到底发生了什么。
这篇文章记录我在过去两年里遇到的三个典型网络故障场景,以及每一层的排查思路和工具链。文末附了一张故障决策流程图,下次凌晨告警可以直接对着走。
网络排查的四层模型
我习惯把网络问题按 OSI 的粒度拆成四层来排查,一层一层往下刨:
| 层 | 典型问题 | 首选工具 |
|---|---|---|
| 连接层(TCP握手/队列) | SYN丢包、backlog满、TIME_WAIT堆积 | ss -s, netstat -an |
| 传输层(TCP重传/窗口) | 重传率过高、零窗口、乱序 | ss -ti, nstat |
| 解析层(DNS/ARP) | DNS超时、ARP缓存异常 | dig, strace -e trace=network |
| 应用层(连接池/超时配置) | 连接池耗尽、idle timeout不匹配 | 应用日志 + tcpdump |
经验法则:从连接层开始,如果能在这里找到答案就不要往下挖。80% 的「网络问题」其实就停在 TCP 连接层面。

场景一:TIME_WAIT 把连接池吃干抹净
某次上线后,服务开始间歇性报 Cannot assign requested address。监控显示同一时间 ESTABLISHED 连接数并没那么高,CPU 也只有 20%。
第一刀:看连接状态分布
$ ss -s
Total: 41280
TCP: 40120 (estab 1024, closed 0, orphaned 0, timewait 38896, synrecv 0)
Transport Total IP IPv6
RAW 0 0 0
UDP 4 2 2
TCP 40120 40116 4
看到没?38896 个 TIME_WAIT,只有 1024 个 ESTABLISHED。这已经不是在「用连接池」了,这是在用 TIME_WAIT 池。
根因分析
TIME_WAIT 是 TCP 主动关闭方在连接关闭后必须保持 2MSL(约60秒)的状态。如果服务频繁创建短连接——比如每次 HTTP 请求都是一个新的 TCP 连接——TIME_WAIT 会快速堆积。当可用端口被耗尽时,新连接就无法建立了。
这里的关键参数:
# 查看当前系统限制
$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
# 可用端口 = 60999 - 32768 = 28231 个
2.8 万个端口,QPS 超过 470 就会在一个 2MSL 窗口内耗尽。这个服务的 QPS 刚好是 500——正好踩在边界上。
修复(按优先级排)
- 上连接池:改 HTTP 客户端为 keep-alive 长连接,QPS 500 只需要十几个连接就能扛住。这个才是治本。
- 开 tcp_tw_reuse:
sysctl -w net.ipv4.tcp_tw_reuse=1,允许复用 TIME_WAIT 状态的连接给新的出站请求。 - 扩端口范围:
net.ipv4.ip_local_port_range = 10240 65535,但这是治标的创可贴。
💡 踩坑记录:
tcp_tw_recycle在 Linux 4.12 之后已经被移除了。如果你在网上看到有人推荐开net.ipv4.tcp_tw_recycle=1,那篇文章至少是 2017 年的。这东西在 NAT 环境下会随机丢掉合法连接——我就是那个花了半天排查「为什么只有部分用户连不上」的倒霉蛋。
场景二:TCP 重传率 18%,用户说「卡」
这个场景更隐蔽。服务没有报错,连接也都正常建立,但用户反馈「页面有时候卡一下,刷新就好了」。APM 显示 P50 延迟 60ms,但 P99 偶尔跳到 2 秒以上。
第一刀:看 TCP 统计
$ nstat -az | grep -E 'TcpRetrans|TcpExtTCPTimeouts'
TcpRetransSegs 3847291 # TCP重传段数
TcpExtTCPTimeouts 128403 # TCP超时次数
重传率怎么算?拿 TcpRetransSegs 除以 TcpOutSegs:
$ nstat -az | grep TcpOutSegs
TcpOutSegs 21038412
# 重传率 = 3847291 / 21038412 ≈ 18.3%
18% 的重传率。正常的应该是 < 0.1%。 这已经不是偶发丢包了,是链路出问题了。
第二刀:用 ss -ti 定位问题连接
$ ss -ti 'dst 10.0.1.50'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 287456 10.0.2.10:34126 10.0.1.50:3306
cubic wscale:7,7 rto:264 rtt:87.5/16.2 ato:40
retrans:0/7 lost:0 sacked:12 reordering:9
pmtu:1500 rcvmss:1448 advmss:1448 cwnd:10
bytes_acked:4127841 bytes_received:139211
segs_out:3847 segs_in:2278
lastsnd:352 lastrcv:352 lastack:348
pacing_rate 8.8Mbps delivery_rate 194.7Kbps
busy:80ms rcv_space:14480 rcv_ssthresh:64088
minrtt:83.2
几个关键指标:
- cwnd:10 — 拥塞窗口只有 10 个 MSS,正常应该是几十上百。说明 TCP 自己检测到了拥塞在主动降速。
- rtt:87.5ms — 对于同机房来说高得不正常(应该是 <1ms)。跨机房或跨地域延迟。
- delivery_rate:194.7Kbps — 实际吞吐只有 24KB/s,基本废了。
第三刀:tcpdump 抓包确认
$ tcpdump -i eth0 host 10.0.1.50 and port 3306 -w /tmp/mysql.pcap -c 5000
然后用 Wireshark 打开(或者在服务器上用 tcpdump -r 直接看),Statistics → TCP Stream Graphs → Time-Sequence (Stevens)。如果看到锯齿状的序列号曲线——确认是丢包导致的重传。
根因最后发现是负载均衡器上的一个健康检查配置问题:health check 间隔设成了 100ms,每台后端机器每秒被打了 10 个 SYN,但代理没开 TCP fast open,大量 SYN 在 LB 的 conntrack 表里排队——间接导致正常流量的数据包被丢。
🔥 这个 bug 我排查了整整一天。问题出在 conntrack 表满和 LB health check 的相互作用上,单独看哪个环节都正常,组合在一起就互相伤害。网络层的排障就是这样——你永远在跟「间接原因」打交道。
场景三:DNS 解析 5 秒超时,拖垮整个服务
有一次,一个 Python 微服务突然开始大量超时。日志全是 socket.timeout: timed out,但下游服务明明健康。CPU/内存/磁盘全绿灯。
第一刀:strace 看系统调用在干嘛
$ strace -e trace=network -p 28471 -T
connect(7, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.0.1.100")}, 16) = -1 ETIMEDOUT <5.001242>
看到了吗?connect 到 DNS 服务器 10.0.1.100:53 花了 5 秒然后超时。但 getaddrinfo 调用在任何日志里都没有出现——因为 Python 的 socket.create_connection() 在底层做了 DNS 解析,业务代码看不到。
第二刀:dig 验证 DNS 服务器状态
$ dig @10.0.1.100 google.com +time=2
;; connection timed out; no servers could be reached
DNS 服务器挂了。但为什么监控没发现?因为健康检查用的是 ping,DNS 进程挂了但主机还活着。
修复
改 /etc/resolv.conf 加备 DNS:
nameserver 10.0.1.100
nameserver 10.0.1.101 # 新增备DNS
options timeout:1 attempts:2 rotate
timeout:1 把 DNS 超时从默认的 5 秒降到 1 秒,rotate 让请求在主备间轮询而不是全压在主 DNS 上。
📌 教训:监控 DNS 服务器不要只 ping。用
dig +short @dns_server your_domain做应用层健康检查,模拟真实解析请求。
实战复盘:MySQL 连接超时 → 全链路排查
最后分享一个完整的排查链路。某天下午,订单服务开始报 MySQL server has gone away,频率从每小时几次涨到每分钟几十次。
Step 1:应用日志
pymysql.err.OperationalError: (2006, "MySQL server has gone away
(BrokenPipeError(32, 'Broken pipe'))")
Broken pipe = 连接在应用层被 RST 了。
Step 2:MySQL 端 slow log
查 MySQL slow query log,没有异常查询——所有请求都在 50ms 内返回。说明不是查询慢。
Step 3:网络层抓包
$ tcpdump -i eth0 port 3306 -w /tmp/mysql.pcap -c 2000
Wireshark 打开,Filter: tcp.flags.reset 1,看到了大量 RST 包。RST 的源 IP 不是 MySQL 服务器,也不是应用服务器——是中间的一台 F5 负载均衡器。
Step 4:F5 配置
F5 的 TCP profile 里 idle timeout 设的是 300 秒。应用端的连接池配置:
# Python DB pool 配置
pool_size = 20
max_overflow = 10
pool_recycle = 600 # 600秒后回收连接
找到了。连接池 600 秒回收连接,但 F5 300 秒就断开空闲连接。300-600 秒之间的连接,应用以为还活着,F5 已经把它杀了。应用发请求→ F5 回 RST → Broken pipe。
修复
pool_recycle = 240(小于 F5 的 300 秒 idle timeout),确保应用在 F5 断连接之前主动回收。
这个问题的诊断链:业务报错 → MySQL slow log(排除查询慢)→ tcpdump(定位 RST 来源)→ 中间件配置(找到根因)。单独看任何一个环节都找不到答案。
快速排查速查表
| 症状 | 第一刀命令 | 看什么 |
|---|---|---|
| 连接拒绝 | ss -tlnp | grep PORT |
端口有没有在监听?backlog 满没? |
| 间歇超时 | ss -ti |
retrans、cwnd、rtt 是否异常 |
| 连接池耗尽 | ss -s |
TIME_WAIT / ESTABLISHED 比例 |
| 响应慢但无报错 | nstat -az | grep TcpRetrans |
重传率是否 > 1% |
| 偶尔 connection reset | tcpdump -i any -w /tmp/dump.pcap |
谁发的 RST? |
| DNS 相关超时 | strace -e trace=network -p PID |
connect 到 53 端口的耗时 |
常见问题
Q: TIME_WAIT 多少算多?
没有固定阈值,取决于可用端口数。简单计算:可用端口数 / 60秒(2MSL) = 安全 QPS 上限。如果你的服务 QPS 接近这个值,就该上连接池或调内核参数了。生产环境 TIME_WAIT 数上万很正常,不用恐慌,先看比例。
Q: tcpdump 在生产环境跑安全吗?
加 -c 5000 限制抓包数量,不用 -w /tmp/(避免塞满磁盘),不要跑 -vvv(verbose 输出本身就是 CPU 开销)。高流量场景用 host IP and port PORT 精确过滤。另外别忘了跑完 kill 掉。
Q: conntrack 表满怎么看?
cat /proc/sys/net/netfilter/nf_conntrack_count 对比 /proc/sys/net/netfilter/nf_conntrack_max。接近上限时内核日志会出现 nf_conntrack: table full, dropping packet。生产环境建议把 conntrack_max 调到 262144 以上。
相关阅读: strace生产环境调试完全指南:从原理到实战,不重启不改代码就能定位疑难问题(2026) | eBPF + bpftrace 生产环境调试实战:不用改代码、不用重启,一行命令定位线上问题(2026) | Linux生产环境CPU 100%问题排查实战:从发现到定位的完整复盘(2026)
总结
网络排障的核心能力不是背命令,是建立分层排查的心智模型:
- 先看连接状态(
ss -s)—— 连接都建立不了就往下查是浪费时间 - 再看传输质量(
ss -ti,nstat)—— 重传率和拥塞窗口比你想象的更能说明问题 - 必要时抓包(
tcpdump)—— 这是终极武器,但也是最后手段 - 别忘了中间设备(LB、F5、防火墙、conntrack)—— 很多时候「网络问题」根本不在两台通信的主机上
下次凌晨告警,先 ss -s,再 ss -ti,然后决定要不要掏 tcpdump。你会发现大部分问题停在第一步就找到了。
本文涉及的生产环境案例均来自个人工作经历,已脱敏处理。工具版本基于 Linux 5.15+ / tcpdump 4.99+。