网站变慢先抓包:tcpdump定位慢在哪一段

一、为什么"服务慢"这句话几乎无法直接排查

站点出现"打开慢"的时候,最常见的描述是"服务器卡了"。但只要真的让报障的人说清楚是哪个 URL、从哪条线路访问、慢的是首字节还是最后一字节,往往就没人答得上来。这不是态度问题,而是"慢"这个概念本身被压缩得太狠了。

一次 HTTP 请求要经过 DNS 解析、TCP 握手、TLS 握手、请求发送、上游处理、响应回传、浏览器渲染这几段。任何一段变慢,用户看到的结果都是"网站卡"。如果不在排查之初就把它切开,后面所有的优化都是猜。

本文讲的是我在单人运维条件下的固定套路:先用 tcpdump 把链路真实发生了什么抓下来,再决定改哪里。它比看监控图更原始,但恰恰因为原始,它不会骗人。

二、动手之前:先问清三个问题

抓包前必须固定三个变量,否则抓到的流量是一锅粥,事后无法归因。

第一个是访问路径。用户是走 CDN、走直连 IP、还是走某个内网入口?这三条路径经过的设备完全不同,抓包位置也应该完全不同。如果用户在 CDN 后面,你在源站抓包,看到的可能是一个"几乎瞬时完成"的请求,因为在源站眼里延迟已经被 CDN 吸收了一部分。

第二个是具体 URL,不是首页。首页可能有页面缓存,某个详情页才是真慢。让用户用浏览器的"复制链接"给出准确地址,包括查询串。

第三个是是否可复现。偶发慢(比如每小时一次)和稳定慢(每次都要 8 秒)是两类完全不同的病。前者往往和定时任务、日志切割、备份或缓存过期周期性触发有关,后者通常是配置或数据量的问题。

三件事问清楚,通常已经能排除掉一半可能性。这一步花五分钟,能省掉后面半小时的瞎抓。

三、抓包的基本姿势:只抓你要抓的那一个

未经限定的 tcpdump 是灾难:几秒钟就能产出几百兆文件,磁盘写满之后问题反而更严重。我的习惯是三条约束同时下:限定主机、限定端口、限定文件大小。

# 只抓本机与某个客户端之间的 443 流量,滚动 5 个 20MB 文件
tcpdump -i any -s 0 -w /tmp/cap.pcap -C 20 -W 5 \
  'host 203.0.113.45 and tcp port 443'

参数含义:-s 0 表示抓完整包(不截断 payload,分析 TLS 和慢响应时必需);-C 20 是每 20MB 换一个文件;-W 5 是最多保留 5 个。这样最坏情况下也只占 100MB。

生产机上如果 tcpdump 不是常用工具,先确认磁盘和 inode 有余量。我遇到过一次抓包把 /tmp 写满,导致 PHP 的 session 文件写不进去,全站用户集体掉登录——本来只是想排查一个页面慢,最后变成了事故。

抓包位置比抓包命令更重要。常见的有三种:

  • 在源站网卡上抓:看到的是 CDN 回源或直连用户的真实流量,适合判断"源站到底慢不慢";
  • 在反向代理(Nginx)上抓:能看到用户侧和上游侧两个方向,适合区分"客户端慢"和"后端慢";
  • 在容器内抓:如果服务跑在 Docker 里,-i any 在宿主机上抓到的可能是 veth 对,需要确认进的是哪一侧。

最省事的判据是:先在后端应用所在的机器上抓。如果这里请求进来和响应出去之间只隔 50 毫秒,那源站没问题,慢在链路上;如果这里就花了 8 秒,那和外网无关,往下挖应用。

四、拆解一次慢请求:从时间轴上读数

把 pcap 拉回本地,我一般不看 Wireshark 的默认视图,而是先看"流"(Follow TCP Stream)的时间戳。真正要做的是把一次请求切成几段,然后各自量时长。

第一段是 TCP 建连。客户端 SYN 到服务端 SYN-ACK 之间的时间就是一次 RTT。如果是国内用户访问国内服务器,这个数应该是个位数毫秒;如果是 200 毫秒以上,说明链路绕了,可能是线路问题、也可能是 CDN 回源走了国际出口。这一步的能量最大,因为它反映的是物理距离,软件优化几乎无解。

第二段是 TLS 握手。完整握手要两个 RTT(TLS 1.2)或一个 RTT(TLS 1.3,且支持 0-RTT 时可以更快)。如果用户每次访问都要完整握手,说明会话复用没生效。判断方法很直接:看第二个连接上有没有 Client Hello 带 session ticket 或 PSK 扩展,如果每次都从头来,问题在服务端的 session ticket 配置或 CDN 的复用策略。

第三段是 请求发出到首字节返回,也就是 TTFB。这一段是最容易出结论的。落到 Wireshark 里就是"客户端发出 HTTP 请求的最后一个包"到"服务端返回第一个响应包"之间的间隔。如果 TTFB 是 7 秒而后续传输很快,那瓶颈 100% 在上游处理,和带宽、和 CDN 都无关。

第四段是 数据传输。这一点常被忽略:如果响应体有 900KB,而实际吞吐只有 100KB/s,那传输要 9 秒,用户体感同样是"卡"。这时要看的是窗口大小、是否有重传、是否被限速。

# 快速统计重传,判断是不是链路丢包导致慢
tcpdump -r /tmp/cap.pcap -nn 'tcp[tcpflags] & tcp-retransmit != 0' | wc -l

重传数量占包总数超过 1%,就值得怀疑链路质量;超过 5%,基本可以确定有实质丢包,这时无论怎么优化后端都不会有明显改善,得换线路或上更好的 CDN。

五、几个一看就懂的典型形态

形态一:SYN 发出后没有回应。pcap 里反复出现同一个 SYN,间隔 1 秒、3 秒、7 秒递增。这通常是防火墙拦截、端口没开、或者目标主机已经宕机。此时应用日志里什么都不会有,因为连接根本没建立。

形态二:建连很快,但请求发不出去。三次握手正常,客户端发了请求,服务端迟迟不回 ACK。这种多半是 accept 队列满或者 worker 全被占住。Nginx 的 listen ... backlog 和 worker_connections 是首先该看的两处,PHP-FPM 的 pm.max_children 打满时也是同样表现。

形态三:响应分两段,中间空了 3 秒。这是最典型的应用层慢:服务端先把响应头发出来了(可能是 Nginx 已经拿到上游的头部),但 body 迟迟不来。排查方向就是上游的 PHP 或数据库,去看慢日志里对应时间点有没有一条 3 秒的查询。

形态四:大量 RST。RST 出现在服务端一侧,通常是应用主动关闭或者连接被中间设备重置。CDN 或负载均衡的健康检查超时就会产生这种包序。如果用户侧出现大量 RST,要怀疑是不是有运营商侧的连接数限制。

这四个形态覆盖了我这些年遇到的八成"网站慢"报障。剩下的两成,通常是 DNS 解析慢或者浏览器端渲染慢,这两类在服务端抓包是看不到的。

六、DNS 与渲染:抓包看不到的那一半

DNS 解析慢是很容易被漏掉的一环。页面上引用了十几个第三方域名(统计、字体、广告、图床),其中任何一个域名解析卡 2 秒,整个页面都会等。tcpdump 抓不了客户的 DNS 请求,但你可以从服务端侧验证:

# 分别看解析耗时(含递归耗时)与各阶段时间
dig +stats www.example.com @8.8.8.8 | grep -E 'Query time|SERVER'

如果自己域名的解析在 50 毫秒以上,就该考虑把权威 DNS 换成响应更快的服务(或者检查是否有 DNSSEC 验证拖慢)。另外,服务端应用里如果对同一域名反复解析(比如 curl 调用外部 API 时不复用连接),也会累积成可观的延迟,这个用 getent hosts 和连接复用配置就能改善。

浏览器渲染慢则是纯前端问题,判断方法是在浏览器开发者工具的网络面板里看"DOMContentLoaded"和"Load"两个时间点。如果服务端响应都很快但页面很久才可用,那是脚本阻塞渲染,和服务端无关。这里最常见的凶手是同步加载的第三方脚本和没有设尺寸的图片。

七、把抓包变成日常习惯而不是救火手段

只在出事时抓包,最大的问题是"出事时已经晚了"。我的做法是给线上留一个轻量的常备通道:

# 常备:只记录慢连接的概况,滚动保留最近 200MB,不抓 payload
tcpdump -i any -s 96 -w /var/log/net/recent.pcap -C 50 -W 4 'tcp port 80 or tcp port 443'

-s 96 只抓包头,体积小到几乎可以常驻,日后出问题可以立刻知道"五小时前那波卡顿到底是丢包还是应用慢",再决定要不要重抓完整包。这是一个成本极低、收益很高的保险。

同时,把 Nginx 的 $request_time 和 $upstream_response_time 都写进访问日志。前者是从读到请求到发出最后一个字节,后者是上游的处理时间。两个数的差就是 Nginx 自己消耗的时间(通常是 TLS 和传输)。只看 $request_time 会把"用户网络慢"误算成"服务器慢",这两个字段配合着看,一眼就能分清责任方。

log_format timed '$remote_addr "$request" $status '
                 'req=$request_time up=$upstream_response_time '
                 'bytes=$body_bytes_sent ua="$http_user_agent"';

八、写排查结论的方式

排查完,结论必须能落到"改什么"上。我的习惯是写成三段:现象(在 pcap 里看到的具体时间轴)、归因(慢在哪一段、排除掉了哪些段)、处置(改哪一个参数、怎么验证改善)。

比如"首页偶发 8 秒"的结论可能是:TTFB 稳定在 7.9 秒,TCP 建连 30 毫秒,TLS 复用正常,上游响应时间日志显示同一时段有慢查询——归因到数据库,处置是给该查询加索引并把连接池调大,验证方式是复查 TTFB 是否降到 300 毫秒以内。

这样的结论老板和同事都能看懂,下次遇到同类问题也有据可循。相比之下,"可能是网络问题,重启了一下好了"这种结论,等于什么也没留下。

九、小结

tcpdump 的价值不在于它有多高级,而在于它把"慢"从一个形容词变成了几段可以计量的时间。抓包前问清三个问题、抓的时候限定范围、分析时按 TCP 建连—TLS—TTFB—传输四段切开,一条链路的问题基本藏不住。

真正需要长期补的其实不是抓包技巧,而是日志里的字段够不够。如果每一次请求的时间都被拆成 Nginx 侧和上游侧两个数记下来,你根本不需要每次都抓包;抓包只是那个"证据链最后一环"。把日志配好,把常备的轻量抓包开着,剩下的就是等下一次报障时平静地打开 Wireshark。

Last modification:November 2nd, 2026 at 12:25 pm

Leave a Comment