4. HTTP头部诊断与可观测性字段

HTTP 请求出问题,应用层日志翻了个遍,状态码也正常,但用户就是反馈「页面打不开」或「响应超时」。这时候,你得往网络层看了。说白了,HTTP 是跑在 TCP 之上的,TCP 连不上,HTTP 就是空中楼阁。

我个人习惯,遇到这类「玄学」问题,第一件事就是掏出 tcpdump 抓个包看看。数据包不会撒谎,它比任何中间件日志都诚实。

4.1 tcpdump 抓包实战:从入门到够用

很多运维同学觉得 tcpdump 参数复杂,其实你只需要记住几个核心场景。我平时排查 HTTP 故障,90% 的情况用下面这几条命令就够了。

4.1.1 基础抓包命令

抓取目标 IP 和端口的流量,是最常用的姿势:

# 抓取 eth0 网卡,目标端口 80 的包,不解析主机名
tcpdump -i eth0 -nn port 80

# 抓取与特定 IP 的通信
tcpdump -i eth0 -nn host 192.168.1.100

# 抓取 HTTP 请求包(只抓前 1500 字节,避免文件过大)
tcpdump -i eth0 -nn -s 1500 port 80 -w http_debug.pcap

-nn 这个参数我强烈建议加上。不加的话,tcpdump 会去反解 DNS,不仅慢,而且有时候 DNS 本身就有问题,反而干扰你。直接看 IP 和端口号,最干净。

4.1.2 常用过滤表达式

场景 过滤表达式 说明
只看 SYN 包 tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0 排查连接发起方
只看 RST 包 tcp[tcpflags] & tcp-rst != 0 排查连接被重置
排除 SSH 干扰 port 80 and not port 22 避免自己 SSH 的包混进来
抓取特定 HTTP 方法 port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354 0x504f5354 是 POST 的 ASCII 码
我的小技巧: 抓包时加上 -s 0 会抓取整个数据包,但文件会很大。生产环境我一般用 -s 1500,够看 TCP 头和 HTTP 头部了。如果怀疑是 body 内容问题,再调大。

4.2 Wireshark 过滤与分析:从数据包到真相

tcpdump 抓完的 pcap 文件,我习惯拖到 Wireshark 里看。Wireshark 的图形界面和过滤语法,比命令行直观太多了。

4.2.1 核心过滤语法

Wireshark 的显示过滤器,和 tcpdump 的捕获过滤器是两套东西。别搞混了。

过滤目标 Wireshark 显示过滤器 用途
指定 IP 对 ip.addr == 192.168.1.1 && ip.addr == 10.0.0.1 只看两个 IP 之间的通信
指定 TCP 流 tcp.stream eq 0 追踪某一次完整的 TCP 会话
重传包 tcp.analysis.retransmission 快速定位网络丢包
零窗口 tcp.analysis.zero_window 排查接收端缓冲区满
HTTP 请求 http.request 只看 HTTP 请求行

我记得有一次线上故障,客户端报连接超时。我抓了包,用 tcp.stream eq 0 看完整流,发现三次握手之后,服务端发了个 SYN+ACK,但客户端没回 ACK。嗯,这就是典型的「半连接」问题,后来查出来是客户端防火墙把 ACK 包丢了。

4.2.2 如何快速定位故障点

打开 pcap 文件后,别急着一条条看。先看 Wireshark 的「Expert Info」选项卡。它会自动标记出异常:重传、重复 ACK、零窗口、乱序包等。这些就是故障的「红灯」。

  1. 看颜色规则: 默认浅紫色是 TCP 异常,黑色是 SYN/FIN 包,红色是重传。一眼扫过去,红色多的,基本就是有丢包。
  2. 看时间列: 如果两次包间隔突然变大(比如从 1ms 变成 200ms),说明中间有延迟或丢包触发了超时重传。
  3. 看 TCP 流图: Wireshark 的「Statistics -> Flow Graph」可以画出时序图,三次握手和四次挥手一目了然。
注意: 别被「重传」吓到。TCP 本身就有重传机制,少量重传是正常的。但如果重传比例超过 5%,或者出现连续重传,那肯定有问题。我曾经遇到一个案例,重传率高达 30%,最后发现是网线接触不良。

4.3 TCP 三次握手与四次挥手故障

HTTP 请求的延迟和失败,很大一部分原因出在握手和挥手阶段。我们一个个来看。

4.3.1 三次握手故障排查

正常的握手是:客户端发 SYN -> 服务端回 SYN+ACK -> 客户端回 ACK。故障通常出现在这三个环节。

故障现象 抓包特征 可能原因
连接超时 只看到客户端发 SYN,没有 SYN+ACK 服务端端口未监听、防火墙拦截入站 SYN
连接被拒绝 客户端发 SYN,服务端回 RST 端口未开放、服务端 backlog 队列满
半连接 客户端发 SYN,服务端回 SYN+ACK,客户端没回 ACK 客户端防火墙拦截 ACK、客户端 SYN 丢失
握手延迟高 SYN 到 SYN+ACK 间隔 > 1s 网络延迟大、服务端负载高、tcp_syncookies 未开启

我遇到过最坑的一次,是服务端开启了 tcp_tw_reuse,导致旧连接的 TIME_WAIT 被复用,新连接收到了旧的 SYN+ACK,直接 RST。嗯,这个坑我踩过之后,再也不敢在生产环境乱开 tcp_tw_reuse 了。

4.3.2 四次挥手故障排查

挥手阶段的问题,通常表现为连接关闭慢、大量 TIME_WAIT 或 CLOSE_WAIT。

  • 大量 TIME_WAIT: 主动关闭方在发完最后一个 ACK 后,会进入 TIME_WAIT,持续 2MSL(约 60 秒)。如果短连接 QPS 很高,TIME_WAIT 会堆积。解决方案是开启 tcp_tw_reuse(客户端)或调整 MSL。
  • 大量 CLOSE_WAIT: 这是应用层 bug 的典型特征。服务端收到了客户端的 FIN,但应用代码没有调用 close(),导致连接一直挂在 CLOSE_WAIT。我曾经排查过一个 Java 应用,就是因为忘了在 finally 块里关闭 Socket,导致 CLOSE_WAIT 涨到几万个,最终 OOM。
  • 挥手丢包: 如果最后一个 ACK 丢了,主动关闭方会重传 FIN。抓包看到 FIN 重传,说明对端没收到 ACK,可能是网络问题或对端处理不过来。
核心原则: TIME_WAIT 是主动关闭方的问题,CLOSE_WAIT 是被动关闭方的问题。看到 CLOSE_WAIT 飙升,别调内核参数,先查代码。

4.4 本章知识体系:网络层追踪全景图

下面这张图,是我自己总结的网络层故障排查路径。从抓包到分析,再到定位握手/挥手问题,每一步都有对应的工具和关注点。

网络层追踪故障排查全景图 网络层追踪故障排查全景图 tcpdump 抓包 Wireshark 分析 内核参数调优 过滤表达式 / 抓包策略 显示过滤器 / Expert Info tcp_tw_reuse / tcp_syncookies 三次握手故障 四次挥手故障 重传 / 零窗口 SYN 无响应 / RST / 半连接 TIME_WAIT / CLOSE_WAIT 重传率 > 5% / 窗口为 0 排查路径:抓包 → 过滤 → 识别握手/挥手阶段 → 定位具体现象 → 调整内核或修复代码 核心原则:数据包不会撒谎,先看包再猜原因

4.5 实战案例:一次 HTTP 502 的抓包排查

最后分享一个真实案例。有一次,线上 Nginx 频繁报 502,后端应用日志没有任何异常。我抓了包,用 Wireshark 打开,过滤 http.response.code == 502,然后右键「Follow TCP Stream」。

你猜怎么着?三次握手成功了,HTTP 请求也发出去了,但服务端在返回 HTTP 响应之前,先发了一个 RST 包。这说明服务端在应用层处理请求时,主动关闭了连接。后来查出来是后端的连接池设置太小,请求排队超时,应用直接调用了 close() 导致 RST。

如果没有抓包,光看 Nginx 日志,你永远不知道是后端主动断开的。这就是网络层追踪的价值——它让你看到「发生了什么」,而不是「你以为发生了什么」。