目录

网络-22 网络故障排查手册

这是系列的最后一篇,也是最实用的一篇。前面各篇讲原理,这里按症状索引——线上出问题时从这里开始查。 配套阅读:网络-10 TCP 实战调优网络-21 抓包实战

1. 排查的基本方法

1.1 分层定位法

网络问题的排查顺序应该沿着协议栈自下而上网络-01),每一层用一条命令确认:

① 物理/链路层  ip link          网卡是否 UP?
② 网络层       ping <网关>      本网段能通吗?
          ping 8.8.8.8     能出公网吗?
③ DNS         dig <域名>       域名能解析吗?
④ 传输层       telnet <IP> <端口>   端口能连上吗?
                nc -zv <IP> <端口>
⑤ 应用层       curl -v <URL>    协议层面对话正常吗?

这个顺序的价值在于快速缩小范围。第 ② 步不通就没必要查 DNS,第 ④ 步不通就没必要查 HTTP 头。跳着查是最常见的时间浪费。

一条命令走完全程:

curl -v -s -o /dev/null -w \
 "DNS:%{time_namelookup} TCP:%{time_connect} TLS:%{time_appconnect} 首字节:%{time_starttransfer} 总计:%{time_total}\n" \
 https://example.com

这是排查慢的第一条命令——它直接告诉你时间花在哪一段,避免盲猜。

1.2 先确认边界

在深入之前,用三个问题把范围切开:

问题 意义
是所有人还是部分人? 部分人 → 怀疑 DNS 缓存、CDN 节点、灰度发布、特定运营商
是所有请求还是特定请求? 特定请求 → 怀疑应用逻辑、大包/MTU、特定后端
什么时候开始的?做了什么变更? 绝大多数故障源于变更。先查变更记录,比盲目排查快得多

第三条最重要:先问"刚才改了什么",往往比任何工具都有效。

2. 症状一:连不上

2.1 完全连不上(Connection refused / timeout)

先区分这两个错误,它们指向完全不同的原因:

错误 含义 排查方向
Connection refused 包到了,但对端明确拒绝(回了 RST) 端口没监听、应用没起来
Connection timeout 根本没有回应 防火墙/安全组丢包、路由不通、IP 写错

refused 说明网络是通的——这是个好消息,问题在应用层。而 timeout 说明包在半路就没了。

refused 的排查:

# ① 服务真的在监听吗?
ss -lntp | grep <端口>

# ② 监听地址对吗?—— 最常见的坑
# 127.0.0.1:8080  → 只能本机访问!外部一定连不上
# 0.0.0.0:8080    → 所有网卡都能访问

监听在 127.0.0.1 而非 0.0.0.0 是"本地测试正常、部署后连不上"的头号原因。容器化场景下尤其常见——容器内监听 127.0.0.1,端口映射就完全失效。

timeout 的排查:

# ① 基础连通性
ping <目标IP>                    # 注意 ICMP 可能被禁,不通不代表端口不通

# ② 路径在哪一跳断了
traceroute -n <目标IP>
mtr -n <目标IP>                  # ★ 持续探测,比 traceroute 更适合看丢包位置

# ③ 端口层面的探测(比 ping 更准)
nc -zv <IP> <端口>
telnet <IP> <端口>

# ④ 本机防火墙
iptables -L -n --line-numbers
systemctl status firewalld

排查顺序有讲究:先查安全组/云防火墙(最常见),再查主机防火墙,最后才怀疑路由。云环境下 90% 的 timeout 是安全组没放开。

mtrtraceroute 更值得用:它持续发包并统计每一跳的丢包率和延迟,能看出是"某一跳彻底断了"还是"某一跳开始丢包"。注意中间跳的丢包不一定是问题——很多路由器对 ICMP 限速,只要最后一跳正常就没事。

2.2 间歇性连不上

# 看队列是否溢出(Linux 默认可能忽略最后 ACK 并重传 SYN+ACK)
ss -lnt                          # Recv-Q 顶到 Send-Q 就是满了
netstat -s | grep -i listen      # overflowed 持续增长

# 端口耗尽(客户端侧)
ss -ant | grep -c TIME-WAIT
sysctl net.ipv4.ip_local_port_range

# conntrack / NAT 状态表耗尽(网关、宿主机、K8s 节点)
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max
dmesg | grep -i conntrack

三个高频原因:

  • accept 队列溢出 → 客户端超时但服务端日志毫无异常,最难排查的一类;
  • 本地端口耗尽 → 报 cannot assign requested address
  • conntrack 表满 → 已有连接正常,新连接间歇丢包;常见日志是 nf_conntrack: table full, dropping packet,原理见 网络-05

3. 症状二:慢

3.1 先定位慢在哪一段

curl -v -s -o /dev/null -w \
 "DNS:%{time_namelookup} TCP:%{time_connect} TLS:%{time_appconnect} 首字节:%{time_starttransfer} 总计:%{time_total}\n" \
 https://example.com

按结果分头排查:

慢在 可能原因 对应篇章
DNStime_namelookup 大) DNS 服务器慢/不可达、K8s ndots 问题、无缓存 网络-11
TCPtime_connect 大) RTT 高(地理距离)、丢包导致 SYN 重传、队列积压 网络-07
TLStime_appconnect 大) TLS 1.2 多一个 RTT、证书链过长、OCSP 查询阻塞 网络-17
首字节time_starttransfer 大) 后端处理慢,不是网络问题 查应用/数据库
总计远大于首字节 传输体积大或带宽不足 压缩、CDN

首字节时间大而前面几段都正常 → 网络没问题,去查后端。这个判断能省掉大量无效排查。

3.2 DNS 慢

# 量化 DNS 耗时
dig example.com | grep "Query time"

# 换个 DNS 服务器对比,判断是本地 DNS 的问题还是域名本身的问题
dig @8.8.8.8 example.com | grep "Query time"

# 看完整解析链路,找出慢在哪一级
dig +trace example.com

K8s 环境优先怀疑 ndots:5网络-11)——访问外部域名会产生 4 次甚至 8 次查询。直接抓包确认:

tcpdump -nn -i any udp port 53
# 看到大量 xxx.svc.cluster.local 的 NXDOMAIN 就是它

3.3 吞吐上不去

# ① 先确认有没有丢包/重传
netstat -s | grep -iE "retrans|lost"

# ② 实测带宽(两端都要装 iperf3)
iperf3 -s                        # 服务端
iperf3 -c <服务端IP> -t 30       # 客户端

两种情况,处理方式完全相反:

  • 有丢包 → 链路质量问题。跨公网考虑 BBR网络-10),内网查网卡/交换机;
  • 不丢包但跑不满 → 很可能是窗口不够(长肥管道)。算一下 BDP:
BDP = 带宽 × RTT
1 Gbps × 100ms = 12.5 MB   ← 需要这么大的窗口才能跑满

默认 tcp_rmem 上限 6MB 会成为瓶颈。此时限制吞吐的不是带宽而是窗口。

3.4 时快时慢

这类问题最难查,几个方向:

# 看是否有周期性的重传或零窗口
tcpdump -nn -w cap.pcap host <对端>
# Wireshark 里过滤 tcp.analysis.flags

# 看连接是否在反复重建(而非复用)
ss -ant | grep -c SYN-SENT

常见根因:

  • 连接没有复用——每次请求重新握手。Go 里查 MaxIdleConnsPerHost(默认只有 2!见 网络-10);
  • DNS 轮询到了不同后端,其中某个慢;
  • 零窗口——对端处理不过来(网络-21 有识别方法);
  • CDN/LB 节点差异——部分节点异常。

4. 症状三:连接中断

4.1 Connection reset by peer

抓 RST 包,关键是判断谁发的(详见 网络-21):

tcpdump -nn -i any 'tcp[tcpflags] & tcp-rst != 0'
RST 来源 原因
服务端 端口未监听、应用崩溃、backlog 满、close() 时仍有未读数据
客户端 超时放弃、连接池丢弃连接
中间设备 防火墙拦截、LB 空闲超时。特征是双方都说不是自己发的

两端同时抓包是定位中间设备的唯一可靠办法。

4.2 长连接每隔固定时间就断

看到"固定周期"这个特征,基本就是超时配置问题

断开周期 怀疑对象
60 秒 Nginx proxy_read_timeout(默认 60s)
几分钟 NAT 网关空闲超时、云 LB 空闲超时
2 小时左右 TCP keepalive 默认值(7200s)终于生效

处置:心跳间隔必须小于链路上最短的那个超时。WebSocket 场景见 网络-19

# Nginx 代理长连接必须加这几行
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;        # ★ 要大于心跳间隔

4.3 CLOSE_WAIT 越来越多

ss -ant state close-wait | wc -l
ss -antp state close-wait | head

直接结论:应用层没有 close(),去修代码。没有内核参数可以调。 详见 网络-10

5. 症状四:DNS 异常

# ① 解析结果对不对
dig +short example.com
dig @8.8.8.8 +short example.com      # 和权威/公共 DNS 对比

# ② 本机用的哪个 DNS
cat /etc/resolv.conf
resolvectl status                     # systemd-resolved

# ③ hosts 是否有残留
cat /etc/hosts

# ④ 清缓存
sudo resolvectl flush-caches          # Linux
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder   # macOS

常见问题对照:

症状 原因
改了 DNS 但还是旧 IP 多级缓存未过期。不要假设 TTL 到了就全网生效,切 IP 时新旧必须并存一段时间
部分用户解析异常 运营商 DNS 缓存/劫持。用 DoH/DoT 验证(网络-11
大域名解析失败,小域名正常 响应超 512 字节需切 TCP,而防火墙只放开了 UDP 53
K8s 内解析外部域名慢 ndots:5 陷阱
解析到了但连不上 DNS 没问题,转去查症状一

“大域名失败、小域名正常"这个特征很典型,指向 TCP 53 被封(网络-11)。

6. 症状五:丢包与大包异常

6.1 小包正常、大包卡死

MTU 黑洞,特征极鲜明,常见于 VPN、隧道、跨云专线:

# 禁止分片,逐步减小找临界值
ping -M do -s 1472 <目标>     # Linux(1472+28=1500)
ping -D  -s 1472 <目标>       # macOS

1472 失败、1420 成功即可确认。原理和处置见 网络-04网络-21

# 网关上做 MSS clamping
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
         -j TCPMSS --clamp-mss-to-pmtu

6.2 持续丢包

mtr -n -c 100 <目标IP>        # 看每一跳的丢包率
netstat -s | grep -iE "retrans|drop"
ip -s link                     # 网卡层面的错误和丢弃计数
ethtool -S eth0 | grep -iE "error|drop"

读 mtr 结果的要点:中间某一跳丢包不一定是问题(路由器对 ICMP 限速很常见)。只看最后一跳——如果最后一跳丢包率为 0,中间的丢包可以忽略。丢包必须是从某一跳开始一直持续到最后一跳,才说明那里有问题。

7. 工具速查表

工具 用途 典型命令
ss 连接状态(替代 netstat,快得多 ss -antpss -lntss -s
netstat -s 协议统计(重传、溢出计数) netstat -s | grep -i retrans
curl -w 分阶段耗时,排查慢的第一选择 见上文
dig DNS 排查 dig +tracedig @8.8.8.8
mtr 路径丢包定位(优于 traceroute) mtr -n -c 100 <IP>
nc 端口连通性 nc -zv <IP> <端口>
tcpdump 抓包(网络-21 tcpdump -nn -s0 -w c.pcap port 443
iperf3 带宽实测 iperf3 -c <IP> -t 30
ip 网卡/路由/ARP ip aip rip neighip -s link
sysctl 内核参数(网络-10 sysctl -a | grep tcp
openssl s_client TLS 握手与证书 openssl s_client -connect host:443
kubectl debug K8s 抓包 --image=nicolaka/netshoot --target=<容器>

8. 快速决策树

出现网络问题
   │
   ├─ 刚做过变更? ────────────► 先回滚/核对变更(最高优先级)
   │
   ├─ 完全连不上
   │    ├─ Connection refused ─► 网络是通的!查监听地址(0.0.0.0 vs 127.0.0.1)、进程状态
   │    └─ Connection timeout ─► 查安全组 → 主机防火墙 → mtr 看路由
   │
   ├─ 能连但慢
   │    └─ curl -w 定位段落
   │         ├─ DNS 慢 ──────► dig +trace;K8s 查 ndots
   │         ├─ TCP 慢 ──────► mtr 看丢包;查 accept 队列
   │         ├─ TLS 慢 ──────► 上 TLS 1.3;查证书链、OCSP
   │         └─ 首字节慢 ────► 网络没问题,查后端应用/DB
   │
   ├─ 连接中断
   │    ├─ RST ──────────────► 抓包判断谁发的;两端同时抓
   │    ├─ 固定周期断 ───────► 查各级 timeout;心跳要短于最小超时
   │    └─ CLOSE_WAIT 堆积 ──► 应用没 close(),改代码
   │
   ├─ 时快时慢 ──────────────► 查连接复用(MaxIdleConnsPerHost)、零窗口、节点差异
   │
   └─ 小包正常大包挂 ────────► MTU 黑洞,ping -M do 验证,做 MSS clamping

9. 三条经验

① 先问变更,再动工具。 绝大多数故障源于变更——发版、改配置、调网络策略、证书过期。查变更记录通常比任何命令都快。

② 分清"网络问题"和"应用问题”。 很多被报为网络故障的问题实际在应用层:CLOSE_WAIT 堆积是没关连接,首字节慢是后端慢,TIME_WAIT 暴涨是没复用连接。curl -w 的分段耗时是划清这条界线最快的工具。

③ 两端同时抓包。 单端抓包只能看到一半真相。判断"包到底有没有发出去"“RST 是谁发的"“中间设备有没有动手脚”,都必须两端对比。


系列到这里结束。回到 网络-00 一个请求的完整旅程 可以看到全系列的考点地图。

10. 本章面试题

Connection refused 和 Connection timeout 有什么区别?各怎么查?

这两个错误指向完全不同的方向:

  • refused:包到了,对端明确拒绝(回了 RST)。说明网络是通的,问题在应用层——端口没监听、进程没起来。重点查监听地址是 127.0.0.1 还是 0.0.0.0(这是"本地正常、部署后连不上"的头号原因,容器场景尤其常见)。
  • timeout:包根本没有回应。查安全组/云防火墙(云环境下 90% 是这个)→ 主机防火墙 → mtr 看路由。

先分清这两个,能省掉大量无效排查。

一个接口变慢了,怎么快速定位是哪一段的问题?

一条命令分段计时:

curl -o /dev/null -w "DNS:%{time_namelookup} TCP:%{time_connect} TLS:%{time_appconnect} 首字节:%{time_starttransfer} 总计:%{time_total}\n" <URL>
  • DNS 慢dig +trace;K8s 查 ndots
  • TCP 慢 → RTT 高或丢包导致 SYN 重传;查 accept 队列
  • TLS 慢 → 升 TLS 1.3、查证书链和 OCSP
  • 首字节慢网络没问题,去查后端应用和数据库
  • 总计远大于首字节 → 传输体积大,上压缩和 CDN

首字节时间是划分"网络问题"与"应用问题"的分界线。

长连接每 60 秒就断一次,为什么?

“固定周期"这个特征基本锁定为超时配置问题

  • 60 秒 → Nginx proxy_read_timeout 默认值;
  • 几分钟 → NAT 网关或云 LB 的空闲超时;
  • 2 小时左右 → TCP keepalive 默认值(7200s)才生效。

处置原则:心跳间隔必须小于链路上最短的那个超时。 Nginx 代理长连接还必须配 proxy_http_version 1.1 和 Upgrade 头,否则握手就失败。

怎么读 mtr / traceroute 的结果?中间某一跳丢包要紧吗?

中间某一跳丢包不一定是问题——很多路由器对 ICMP 限速或低优先级处理,会显示丢包但实际转发正常。

只看最后一跳:最后一跳丢包率为 0,中间的丢包都可以忽略。

真正的问题特征是:从某一跳开始丢包,并一直持续到最后一跳。这说明那个位置有实际问题。

mtr 优于 traceroute,因为它持续发包并统计丢包率和延迟分布。

带宽够但吞吐跑不满,怎么判断原因?

先看有没有丢包,两种情况处理方式完全相反:

netstat -s | grep -iE "retrans|lost"
iperf3 -c <IP> -t 30
  • 有丢包 → 链路质量问题。跨公网上 BBR,内网查网卡/交换机;
  • 不丢包但跑不满窗口不够(长肥管道)。算 BDP:带宽 × RTT,如 1Gbps × 100ms = 12.5MB,而 tcp_rmem 默认上限 6MB 会成为瓶颈。此时限制吞吐的是窗口而非带宽。
排查网络问题的第一步应该做什么?

先问"刚才改了什么”。

绝大多数故障源于变更——发版、改配置、调整网络策略、证书过期。查变更记录通常比任何工具都快。

然后用三个问题切分范围:所有人还是部分人?(部分人 → DNS/CDN/灰度/特定运营商)所有请求还是特定请求?(特定 → 应用逻辑/大包 MTU)什么时候开始的?

最后才是分层定位:ip linkping 网关ping 公网dignc -zvcurl -v沿协议栈自下而上,跳着查是最常见的时间浪费。