网络-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 是安全组没放开。
mtr 比 traceroute 更值得用:它持续发包并统计每一跳的丢包率和延迟,能看出是"某一跳彻底断了"还是"某一跳开始丢包"。注意中间跳的丢包不一定是问题——很多路由器对 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
按结果分头排查:
| 慢在 | 可能原因 | 对应篇章 |
|---|---|---|
DNS(time_namelookup 大) |
DNS 服务器慢/不可达、K8s ndots 问题、无缓存 | 网络-11 |
TCP(time_connect 大) |
RTT 高(地理距离)、丢包导致 SYN 重传、队列积压 | 网络-07 |
TLS(time_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 -antp、ss -lnt、ss -s |
netstat -s |
协议统计(重传、溢出计数) | netstat -s | grep -i retrans |
curl -w |
分阶段耗时,排查慢的第一选择 | 见上文 |
dig |
DNS 排查 | dig +trace、dig @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 a、ip r、ip neigh、ip -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 link → ping 网关 → ping 公网 → dig → nc -zv → curl -v。沿协议栈自下而上,跳着查是最常见的时间浪费。
xingliuhua