"网站慢"这句话在三个群里会得到三个答案。运维说机器 CPU 12%、带宽没打满,不是他的问题;开发说本地跑同一个接口几十毫秒,不是代码问题;用户说就是打不开。三方都没说谎,因为他们看的是不同的层。排障的第一步不是查,是把现象归到层上——层判断错了,后面每条命令都在给错误假设找证据,越查越像有道理。
决策视角只有三件事:哪类现象先怀疑哪一层、每层的最小验证动作是什么、什么时候该停止网络排查。
1. 先分类现象,再谈分层
"慢"和"打不开"必须拆成可验证的五类。分类只问三个问题:是不是所有人都受影响、是不是所有请求都受影响、出错发生在拿到第一个字节之前还是之后。
| 现象 | 典型表现 | 优先怀疑 | 经验分布(判断用,不是实测) |
|---|---|---|---|
| 连不上 | 超时、Connection refused、reset |
L3/L4:路由、安全组、端口 | 连接层多数,DNS 次之,应用最少 |
| 连上很慢 | TCP 建了,首字节迟迟不来 | L7:应用排队、上游依赖 | 应用为主,TLS 与 TCP 各占小头 |
| 时快时慢 | p50 正常、p99 爆掉 | 资源池、GC、回源、限流 | 池与 GC 为主,链路为辅 |
| 部分用户不行 | 同一 URL 有人好有人坏 | DNS、运营商、地域 | DNS 与运营商为主 |
| 特定请求不行 | 只有某接口、某参数、某文件 | 应用、网关规则、大小限制 | 几乎全在应用与网关 |
分类的价值在排除:一旦确认所有请求、所有用户都慢,DNS 与客户端版本可以直接划掉。
2. 分层定位的最小动作集
五个探针按顺序打,每段只回答一个问题,段与段之间做减法。不要一上来就抓包——抓包回答不了"慢在哪一段"。
$ curl -o /dev/null -s \
-w 'dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
https://TARGET/health
dns=0.004 tcp=0.031 tls=0.089 ttfb=0.412 total=0.418
| 段 | 减法 | 这一段慢说明什么 | 下一步 |
|---|---|---|---|
| DNS | time_namelookup |
解析器远、递归慢、权威超时 | dig 对比本地与公共解析 |
| TCP | connect - namelookup |
路由绕、SYN 排队、backlog 满 | mtr、看 ListenOverflows |
| TLS | appconnect - connect |
证书链拉取、OCSP、无会话复用 | openssl s_client -status |
| 首字节 | starttransfer - appconnect |
服务端处理、排队、下游依赖 | 换应用侧线程栈/慢日志 |
| 内容 | total - starttransfer |
传输窗口、压缩、分块、客户端读得慢 | 看响应体大小与 Content-Encoding |
纪律:单次 curl 只当探针,不当结论。至少跑 20 次看分位,别把一次冷启动的 DNS 当常态。
3. "能 ping 通但打不开"说明了什么
它只说明一件事:ICMP 到那台机器的路径通。ICMP 和 TCP 443 走的不是同一条策略链,中间设备对二者的处理经常完全不同。
| 现象组合 | 更可能的层 | 判定动作 |
|---|---|---|
ping 通、curl 超时 |
端口没监听或被安全组挡 | ss -lntp 在本机看监听,从外侧 nc -vz |
ping 通、立刻 refused |
进程没起或绑了 127.0.0.1 | ss -lntp 看绑定地址 |
| ping 通、TLS 阶段失败 | 证书/SNI/中间设备拦截 | openssl s_client -servername TARGET |
| ping 通、返回 403/421 | 网关按 Host 头或 SNI 分流 | 带 -H 'Host: ...' 复测 |
| ping 通、只有大响应卡住 | MTU/分片、窗口、代理缓冲 | 小文件对照 + ping -M do -s 1472 |
反过来 ping 不通而页面正常也常见:ICMP 被丢弃、TCP 放行。用 ICMP 判断可用性从一开始就是错的探针。
4. 延迟高和丢包是两件不同的事
两者都表现为慢,修复手段互不通用:RTT 高要改路径,丢包要改队列与链路。混着查只会得出"扩容"这类无效结论。
| 信号 | 含义 | 常见误判 |
|---|---|---|
| RTT 整体抬高、方差小 | 路径变长(绕路、跨地域、代理多一跳) | 误判为"带宽不够" |
| RTT 稳定但重传率高 | 链路质量差、拥塞丢包 | 误判为"服务端慢" |
| RTT 抖动大、重传随之上行 | 队列积压(bufferbloat) | 误判为"对端处理不过来" |
| 只有尾包重传 | 中间设备限速或 MTU 黑洞 | 误判为"应用写响应慢" |
| 建连耗时高于传输耗时 | 握手阶段问题,与带宽无关 | 误判为"要加机器" |
$ nstat -az TcpRetransSegs TcpExtTCPTimeouts TcpExtListenOverflows TcpExtTCPLostRetransmit
$ ss -s | head -5 # 全局 socket 与重传概况
$ mtr -rwzc 100 TARGET # 逐跳丢包:末跳丢 vs 中间跳丢
判据:逐跳丢包只有末跳丢才算真丢,中间跳丢包往往是路由器对 ICMP/UDP 探测限速。mtr 的中间跳 20% 丢包而末跳 0%,说明链路是好的。
5. 时快时慢的四个来源
p99 爆炸而 p50 正常,几乎不可能是网络设备——设备故障会让所有请求一起慢。四个来源有各自的指纹:
| 来源 | 指纹 | 排除动作 |
|---|---|---|
| 连接池耗尽 | 慢请求成批出现,间隔约等于获取超时;等待线程堆积 | 打池指标:活跃/等待/获取耗时 |
| GC 停顿 | 慢请求呈秒级块状,与堆曲线同步 | 对照 GC 日志时间戳,看是否同一秒 |
| CDN 回源 | 只在缓存未命中或过期后出现,源站 QPS 尖刺 | 看回源率与缓存命中率,X-Cache 头 |
| 上游限流 | 慢与 429/503 同时出现,集中在某一依赖 | 看依赖侧限流计数与重试次数 |
四个来源的共同点是排队,区别是排在哪。先看是否所有实例同时慢(是 → 共享依赖;否 → 单实例资源),再对齐 GC 或缓存过期的时间点。
6. 部分用户不行:归因顺序不能乱
顺序错了会白跑:先查客户端版本,会漏掉整片地域。
- 地域:同一 URL 从两个地域探活,差异稳定即为地域问题;
- 运营商:同地域换出口线路,差异稳定即为线路问题;
- DNS 解析差异:不同解析器返回不同 IP,落到不同源站或过期记录;
- 客户端版本:最后才查,因为它只解释"个体差异",解释不了"整片失败"。
$ dig +short TARGET A @LOCAL_RESOLVER
$ dig +short TARGET A @8.8.8.8
$ dig +trace TARGET | tail -20 # 看权威是谁、委派断在哪
第 3 步最常见的坑是解析正确但缓存了旧 IP:TTL 过长,切换后仍有大量用户打到旧地址,表现为"失败比例随时间下降",常被误判成服务端不稳。
7. 代理与网关链路:找到最少可复现的一跳
链路越长越要切开:客户端 → 边缘 → 网关 → 应用。方法只有一句:从离故障最近的一跳开始向两端扩张,直到第一个能稳定复现的跳。
# 同一路径、同一 Host,只改连接目标
$ curl -s -o /dev/null -w 'edge=%{time_starttransfer}\n' https://EDGE_HOST/health -H 'Host: TARGET'
$ curl -s -o /dev/null -w 'origin=%{time_starttransfer}\n' http://ORIGIN_IP:PORT/health -H 'Host: TARGET'
两边都要带相同的 Host 与路径,差异才可信。反向代理场景还要核对三处:转发头是否被覆盖、超时是否逐层放大(网关 60s、代理 30s、应用 5s,用户等到的是 60s)、保活复用是否被中间层打断。
8. 观测盲区:看不到不等于没问题
| 盲区 | 为什么看不见 | 替代手段 |
|---|---|---|
| NAT/SNAT 端口耗尽 | 应用侧只看到"连不上对端" | 看 NAT 网关的转换表与失败计数 |
| 负载均衡健康检查 | 检查通过但真实请求失败(路径不同、Host 不同) | 用真实请求复测检查路径 |
| 云厂商内网与安全组 | 主机内抓包看不到被丢弃的包 | 用 VPC 流日志 / 安全组拒绝计数 |
| 容器网络 | 抓的是容器 veth,看不到节点侧丢包 | 节点侧抓 cni0/eth0,对照两侧计数 |
| TLS 中间设备 | 应用日志一切正常,握手却慢 | 对比两侧握手耗时与证书链长度 |
处理原则:先补观测,再谈归因。缺观测时靠猜,猜错还会留下"已验证"的错误结论。
9. 证据链:抓包与时间戳对齐
$ date -u +%Y-%m-%dT%H:%M:%S.%3N # 记录本机时间基准
$ tcpdump -i eth0 -nn -s0 -w cap.pcap 'host PEER_IP and port 443'
$ tshark -r cap.pcap -q -z io,stat,1 -z expert
$ tshark -r cap.pcap -Y 'tcp.analysis.retransmission' | wc -l
三件必须同时保存:抓包文件、两端时间基准、请求标识(trace id 或 URL+时间)。只有抓包无法与应用日志对齐;只有日志无法证明包发出去了没回。
10. 排错:五种典型误判
| 现象 | 真实原因 | 处理 |
|---|---|---|
curl 首字节 400ms,应用日志显示 5ms |
慢在等排队/等连接获取 | 看池等待指标,不是看业务耗时 |
| 只有部分实例慢 | 单实例资源或该实例到依赖的链路 | 按实例维度切分指标,别用全局均值 |
| 加带宽后没变化 | 瓶颈在握手或应用排队 | 回到分段计时,确认段再说优化 |
| 抓包里大量重传但用户无感 | 重传被快速恢复,未触及超时 | 以尾延迟为准,别以计数为准 |
| 换 DNS 后短暂更糟 | TTL 未过期、解析结果混用 | 缩短 TTL、灰度切换、观察收敛时间 |
11. 边界:什么时候别做网络排查
| 场景 | 该做什么 | 理由 |
|---|---|---|
| 单机本地进程间调用出问题 | 直接看进程与日志 | 没有链路可分层,分层只是仪式 |
| 已确认是应用逻辑错误 | 修逻辑 | 网络排查只会延后修复时间 |
| 没有链路观测、也没有分段计时 | 先补观测 | 无数据的归因等于猜 |
| 慢只出现在压测工具侧 | 查压测机自身 | 工具端资源与连接数常是瓶颈 |
| 协议层一切正常但业务结果错 | 查数据与逻辑 | 传输正确不等于语义正确 |
一条判据收尾:先分层、再分段、后抓包。分层为了少查,分段为了知道该抓哪段,抓包留最后是因为它最贵。
12. 自测
- 用户说"网站慢",你要先问哪三个问题才能把它归到五类中的一类?
time_connect与time_namelookup的差值变大,最可能是什么环节?- 能 ping 通但 HTTPS 打不开,为什么不能据此排除网络问题?
- 逐跳
mtr里中间跳丢包 20%、末跳 0%,结论是什么? - p50 正常而 p99 突增,为什么优先怀疑连接池而不是网络设备?
- 只保留抓包文件、不保存两端时间基准,事后会失去什么能力?