恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Wireshark抓包实战:TCP连接生命周期如何引发接口间歇性超时
首页
资讯中心
/
Wireshark抓包实战:TCP连接生命周期如何引发接口间歇性超时
Wireshark抓包实战:TCP连接生命周期如何引发接口间歇性超时
发布时间:2026/9/25 11:35:16
看到“WWWWWWWWWWWWW”这串 W我第一反应不是电量耗尽乱敲键盘而是想起那天抓包软件里刷出来的波形图——一连串尖刺层层叠叠像海浪一样拍在时间轴上。那次排查花了我整整一个晚上最后发现问题的根子不在网络却藏在一堆 TCP 连接的生命周期里。所以今天这篇也想从一串 W 写起它既能是网线里的波动也能是 Wireshark 的 W更能是很多人反复踩中的“网络真凶”留下的波纹。这篇文章写给天天和后端、运维、SRE 打交道或者自己在排查接口超时却迟迟找不到证据的开发者。我会用一次完整的真实排查过程把 Wireshark 怎么抓包、怎么看包、怎么从一堆看似正常的流量里挖出“间歇性超时”的根因全部讲清楚。我会尽量保留现场的操作顺序和判断过程包括我中间走偏的地方。如果你能跟着走一遍下次再遇到“重试一下就恢复”的诡异故障至少知道从哪一刀切下去。1. 那串 W 背后的真实现场间歇性超时与“查不出来”的网络1.1 现象特征固定时间段、特定接口、重试即恢复先说当时的现场。业务方反馈线上某个核心查询接口在每小时的固定几分钟内会出现间歇性超时客户端拿到的要么是 504要么是连接被重置。每次超时持续大概几十秒到一分钟过了这个窗口什么都不用做接口自己就恢复了。运维最开始的处理方式很简单加超时时间、调大重试次数。结果确实能掩盖问题——毕竟报错率没涨业务方也接受“偶尔慢一下”。但这不是长久之计因为随着流量增长这个间歇性超时的窗口在变长。我当时拿到手的日志有三个特征第一超时集中在每小时的 07 分、22 分、37 分、52 分左右非常规律。第二超时请求的分布不均匀同一拨请求里有的秒回有的直接卡死。第三客户端重试后基本都成功很少连续失败两次。这几个特征组合在一起已经能排除掉“网络出口抖动”“运营商链路问题”这类完全随机的故障更倾向于应用层或连接层有周期性的资源竞争。1.2 为什么前三板斧失效ping 通、mtr 正常不等于 TCP 没问题按常规排查顺序我先让网络同事测了 ping 和 mtr。结果很有意思从客户端到服务端的丢包率为 0延迟也很平稳带宽占用不高路由器层面没有任何丢包记录。于是大家一度怀疑是应用代码的问题开始查慢查询、GC、锁竞争折腾了大半天也没结果。这里有个非常容易踩的认知误区ping用的是 ICMP 协议路径可能和业务 TCP 流量完全不同而且很多网络设备对 ICMP 有单独的高优先级处理。mtr只能证明“网络设备的转发面”通不能证明“TCP 协议的连接建立和数据传输”没问题。TCP 层面的重传、乱序、连接队列溢出ICMP 和 mtr 全都看不到。所以当应用层排查无果、网络设备又一切正常的时候唯一的出路就是把真实业务流量抓到包里看。这也是我坚持上 Wireshark 的原因。2. Wireshark 在这个局里的位置部署方案与抓包现场2.1 抓包位置双端同时抓而不是只抓一端很多人第一次抓包会习惯性地在服务器上tcpdump抓完发现全是正常流量什么问题都看不出来。这不是没抓到而是抓漏了。TCP 是端到端协议服务端看自己的视角和客户端看自己的视角完全不一样如果服务端发出 SYN-ACK 后一直没等到 ACK服务端视角看到的是重传客户端视角看到的可能是自己发的 ACK 早被中间设备丢了。只抓一端永远只能知道一半真相。所以我在这次排查里做了双端抓包在客户端所在的接入机和服务端实例上同时起 tcpdump然后对齐两边的包做对比。两边抓到同一笔请求的握手、传输、挥手全过程才能判断异常到底发生在哪个环节。这里有个现实问题生产环境不是实验室不能随便把网卡设成混杂模式抓所有包。比较稳妥的做法是精准过滤只抓目标服务的 IP 和端口。2.2 tcpdump 参数一次抓到点子上别把磁盘写爆我在客户端和服务端分别用了下面这套命令tcpdump -i eth0 -s 96 -w /data/capture-$(date %s).pcap \ host 10.0.0.12 and tcp port 8080 -B 4096 -G 300 -W 4逐项说一下每个参数的理由。-s 96是最关键的取舍snaplen 只抓每个报文前 96 字节对 TCP 头分析完全够用因为我们要看的是序号、确认号、标志位、窗口大小这些都在 TCP 头里如果后面需要看 HTTP 明文内容再考虑抓完整包。-G 300 -W 4是做文件轮转每 5 分钟写一个新文件最多保留 4 个避免长时间抓包把磁盘写满。-B 4096把内核缓冲区调到 4MB高并发下防止 tcpdump 自己丢包——抓包工具丢包是最隐蔽的陷阱它会让你误以为网络在丢包。抓了大约 30 分钟两个端各产生了约 800MB 的 pcap 文件。文件不小但因为有 IP 和端口过滤里面的流量基本都和目标服务相关后续分析不算太累。2.3 Wireshark 打开 pcap 后的第一轮阅读先看宏观再抠细节打开 pcap 文件之后我没有立刻翻包而是先做了三件事。第一把时间显示格式改成相对时间。View - Time Display Format - Seconds Since Previous Captured Packet。这样能看到包与包之间的间隔比看绝对时间直观得多。第二看 Expert Info。Wireshark 会自己标注异常包比如重传、重复 ACK、乱序、校验和错误。这一眼扫过去能建立全局印象异常集中在哪个阶段数量级是多少。第三画一张 IO Graph。Statistics - IO Graph把 Y 轴设为 Packets/Tick过滤器分别填tcp.analysis.retransmission、tcp.analysis.duplicate_ack、tcp.flags.syn1。图形出来之后我整个人都精神了每隔 15 分钟就有一波纯尖峰和业务日志里的超时窗口完全重合。这意味着问题一定和周期性触发的流量模式有关而不是随机网络事件。3. 第一波涟漪SYN 反复重传与 accept 队列打满3.1 时间轴上为什么出现了一串 S打开异常时间窗口里的 TCP 流我发现一个非常典型的画面客户端发了一个 SYN等了一会儿没回包又发了一个 SYN再等又发。其中一个连接在 1 秒、2 秒、4 秒的间隔里连续重传了三次才最终完成握手。在 Wireshark 的包列表里这几条 SYN 被标成黑色或者浅红色说明触发了tcp.analysis.retransmission和tcp.analysis.fast_retransmission。这里要解释一个机制TCP 握手阶段的 SYN 重传和后续数据段的重传不太一样。SYN 发送后如果客户端在初始 RTO重传超时时间通常 1 秒左右内没有收到 SYN-ACK就会重传并且每次重传的超时时间指数退避1 秒、2 秒、4 秒、8 秒。所以当你在包里看到间隔翻倍的多条 SYN基本可以断定服务端的 SYN-ACK 没有按预期返回。要么是 SYN 被中间设备丢了要么是服务端根本没能力处理这个 SYN。3.2 服务端视角SYN-ACK 发出去了但 ACK 等不到再看服务端抓到的包我发现更膈应的情况服务端确实发出了 SYN-ACK但紧接着客户端就重传 SYN 了。也就是说SYN-ACK 可能在网络上绕了一圈客户端没收到或者在客户端收到之前客户端已经进入了重传流程。顺着这个现象查服务端状态ss -lnt的输出让我倒吸一口凉气State Recv-Q Send-Q Local Address:Port Peer Address:Port SYN-RECV 0 1 10.0.0.12:8080 10.0.0.13:52340 ESTAB 0 512 10.0.0.12:8080 10.0.0.13:52342Recv-Q 和 Send-Q 都异常ESTAB 状态的连接背了一大堆数据。更关键的是 SYN-RECV 状态的半连接在堆积说明全连接队列已经满了应用程序来不及 accept内核直接把新的 SYN 丢弃。这就是客户端不断重传 SYN 的根因不是线路丢包是服务端没地方放新连接了。3.3 修复半连接堆积somaxconn 与 backlog 的一连串调整当时我做了两个调整。第一把net.core.somaxconn从 128 调到了 4096第二把应用监听 socket 的 backlog 从默认值调大比如 Nginx 的listen 8080 backlog1024。这两个参数的关系必须搞清楚somaxconn是内核允许的全连接队列上限应用层的 backlog 是应用向内核申请的队列长度实际生效值取两者中较小的那个。如果只调应用不调内核等于白调。调整之后SYN 重传的数量明显下降但还没有完全消失。这个阶段告诉我队列溢出只是表层问题更重要的是为什么短时间内会突然涌进来这么多新连接。这就引出了下一层问题。4. 被 Wireshark “冤枉”的 Checksum 与 offload 假乱序4.1 连续 Dup ACK看起来像丢包但两端都说自己没丢继续往下看传输阶段我又发现一批标记为tcp.analysis.duplicate_ack的包。客户端在很短的时间里对同一个数据段连续发出重复 ACKWireshark 甚至标了 Fast Retransmission快速重传。正常情况下重复 ACK 意味着接收方看到了序号断层也就是丢包。但诡异的是两端网卡统计里的丢包计数都是 0交换机的错误计数也是 0。这时候我犯过一个错误差点把锅甩给中间链路要求网络同事去查光模块和交换机端口。还好在准备提工单之前我做了一件正确的事——点开几个被 Wireshark 标记为错误校验和的包仔细看了看。4.2 Checksum 红色报错的真相网卡把校验和计算“卸载”了Wireshark 里大量 TCP 段被标成红色 Checksum 错误很多人看到这里会直接说“包都坏了网络有问题”。但这其实是抓包的一个经典误报源现代网卡普遍支持checksum offload也就是说数据包在传给网卡时校验和字段可能是空的或者不对的由网卡硬件在发送时计算并填充。tcpdump 抓包发生在协议栈和驱动层之间它抓到的是网卡“还没来得及计算校验和”之前的包所以 Wireshark 才会显示校验和错误。怎么确认这是 offload 的假象而不是网络真的坏了方法很简单看两端网卡的统计计数。ethtool -S eth0里rx_crc_errors、rx_error这些字段如果全是 0说明物理链路上根本没有坏包。再配合一条命令验证ethtool -k eth0 | grep checksum看到tx-checksum-ipv4: on就可以放心忽略 Wireshark 的 Checksum 报错。这个经验我后面又用到过很多次抓包工具看到的现象和物理链路实际发生的现象是两回事。4.3 GRO/GSO 与“超大帧”的误导另一件让我差点误判的事是 Wireshark 里出现了不少 len 超过 1518 的“巨型帧”。第一反应是 MTU 不一致导致分片查了一圈发现 MTU 配置没问题。最后定位到是 GROGeneric Receive Offload在接收方向把多个小包合并成一个逻辑大包tcpdump 抓到的这个“大包”是合并后的形态不代表线路上真的有超大帧。GSO 是发送方向的对称机制。当这些 offload 机制和前面的重传、Dup ACK 叠加在一起时画面变得特别混乱看起来像丢包加乱序加超大帧实际物理链路干净得很。这里最关键的动作不是直接关闭 offload——生产环境关掉 offload 通常只会增加 CPU 开销效果未必好——而是在测试环境做一轮对照关掉 GRO/GSO 后重试同样的流量如果抓包里的“异常”消失但业务行为没有任何变化就证明这些异常是卸载机制导致的观测假象。我做了这个对照果然如此。4.4 那真正导致超时的重传来自哪里既然物理链路没问题为什么还会出现触发快速重传的重复 ACK答案是这些重传的一部分是被服务端全连接队列溢出带出来的叠加效应。前面握手阶段 SYNs 积压建立起来的连接被迫排队应用来不及读数据接收窗口收缩发送端被迫减速加上定时批量请求一拥而上形成了短时间内的拥塞窗口塌陷。也就是说传输层的“假乱序、假重传”是症状真正的病灶还是“连接突发太多太集中”。这一层分析让我彻底放弃在“是不是网络设备丢包”上继续纠缠把注意力收回到连接生命周期本身。于是最后一个关键疑点浮出水面为什么这些连接都活得这么短然后大量进入 TIME_WAIT5. 连接生命周期太短TIME_WAIT 池与丢失的连接复用5.1 抓包里的挥手泛滥每个请求都新建连接在异常窗口里我注意到一个刺眼的统计数字TCP 流的平均生命周期只有 3 秒左右而且几乎每个请求结束后都是客户端主动发起四次挥手也就是客户端先发 FIN。这意味着什么意味着应用没有复用连接发完一个 HTTP 请求就断开下一个请求再重新建立连接。请求量一大握手风暴和挥手风暴同时出现服务器所有的精力都花在“创建连接”和“销毁连接”上反而没空处理真正的业务数据。这不只是性能浪费而是直接和前面的 accept 队列溢出挂钩同样 QPS 下如果连接能复用每秒新增连接数可以降到原来的几十分之一如果每个请求都新建连接那么新增连接速率就等于 QPS队列当然会被瞬间打爆。5.2 TIME_WAIT 堆积客户端源端口快不够用了我再看了一眼客户端侧的统计TIME_WAIT 状态连接数已经逼近三万。TIME_WAIT 是主动关闭连接的一方在发送完最后一个 ACK 之后进入的状态目的是保证对端重传的 FIN 还能被正确处理。这个状态默认持续 60 秒由net.ipv4.tcp_fin_timeout控制。然后做一个简单的算术如果客户端源端口大约有 28,000 个可用按常见 ephemeral 端口范围 32768-60999 算而每个连接要在这个端口上停留 60 秒才释放那么每秒新建连接数只要超过大约 470 个就会开始出现端口不够用的情况。业务高峰期每秒新增连接数远超这个值于是大量新连接找不到源端口只能排队等待或者直接失败。日志里的“重试即恢复”本质就是高峰过去、TIME_WAIT 释放了端口新连接才得以建立。5.3 内核参数救急tcp_tw_reuse 为什么不能乱开网上关于 TIME_WAIT 太多最常见的救法就是开net.ipv4.tcp_tw_reuse。这个参数的意思是允许内核在新建连接时复用还在 TIME_WAIT 状态的连接前提是双方都开启了 TCP 时间戳tcp_timestamps。我当时没有立刻开因为有两个顾虑。第一tcp_tw_reuse只对发起连接的一方有效也就是说它是客户端的解法服务端堆 TIME_WAIT 多开了它也没用。第二复用连接存在旧包串扰风险如果老连接里有一个迟到很久的报文正好新连接复用了同一组四元组并且序号区间重叠接收方可能把旧包误认为新连接的数据。虽然时间戳机制能降低这种概率但在高强度流量下不值得赌。至于tcp_tw_recycle我直接不建议在生产环境碰它在 NAT 环境里会引发更隐蔽的丢包问题。5.4 真正的根治方向连接池与 HTTP keep-alive所以TIME_WAIT 堆积只是结果不是原因。根本问题是应用把 TCP 当成一次性的玩具用完了就扔。修复方案也顺理成章在应用层开启 HTTP keep-alive让客户端和服务端之间的 TCP 连接尽量复用对内部服务调用引入连接池把连接数控制在固定水位同时把周期性触发的批量请求打散避免整点并发猛冲。我当时的改动清单里最有效的一行其实是把 HTTP 客户端从“每次请求新建连接”改成“连接池复用 空闲保活”。改完之后同一套业务量下每秒新增连接数降了将近 90%TIME_WAIT 数量从三万降到两千多SYN 重传和 accept 队列溢出的现象也随之消失。至此间歇性超时的问题才算真正闭环。6. 修复清单与复盘让问题不再“重试即恢复”6.1 最终改动清单这次排查最终落到纸面上的改动我整理成了表格方便以后直接对照调整项调整前调整后说明应用连接复用每个请求新建连接连接池 keep-alive最关键的一步降低新增连接速率定时批量任务整点瞬时全量发起分片、限流、错峰消除周期性尖峰net.core.somaxconn1284096扩大全连接队列上限Nginx backlog默认值1024和应用层配合取两者较小值抓包观测只看一端双端同时抓包还原完整 TCP 视角内核 TIME_WAIT 参数保持默认保持默认用底层修复替代参数救急我特意把最后一行写进去是想提示一点不要为了显示自己有本事硬开一堆不明所以的内核参数。这次问题如果只靠开tcp_tw_reuse和调小tcp_fin_timeout去救大概率能顶一阵子但很快会在别的地方重新冒出来。连接池是治本参数只是掩盖。6.2 用“周期性”反推根因的方法复盘时我又验证了最早那个“每隔 15 分钟”的特征。客户端业务侧确实存在一个定时任务每小时的第 07、22、37、52 分钟会批量拉取一批数据命中同一个服务实例而且这个客户端代码库里的 HTTP 调用没有开启连接复用。两条线索一拼一切都对上了周期性任务触发 - 瞬时大量新连接 - accept 队列溢出 - SYN 重传 - 部分请求超时 - 客户端重试时高峰已过 - “重试即恢复”。以后遇到任何“重试就好了”的故障我都会先问三个问题故障时间点之间有没有固定间隔故障期间的 TCP 新建连接速率是不是异常高请求在故障后重试为什么就一定成功如果这三个问题都有明确答案大概率不是网络问题而是连接生命周期或队列容量问题。6.3 沉淀下来的 Wireshark 操作习惯经过这次我把 Wireshark 的使用方式固定成了一组自己的套路。显示过滤器只保留这几条tcp.analysis.flags tcp.analysis.retransmission tcp.analysis.duplicate_ack tcp.flags.syn1 tcp.flags.fin1打开 pcap 后先看 Expert Info再看 IO Graph最后才跟进单个 TCP 流。右键 - Follow - TCP Stream可以还原一次完整会话Statistics - Flow Graph能看连接生命周期时序。如果包特别多用命令行工具tshark做聚合统计比图形界面更快比如统计每个连接的首包时间、结束时间、重传次数直接定位异常流。另外还有一个容易被忽略的点双端抓包时注意两边的时钟偏差。如果两边系统时钟差太多按绝对时间对齐会得出完全错误的结论。我一般先在包里找一个两边都看得到的“同时事件”比如同一笔请求的握手用相对间隔来对齐时间线。6.4 一些不再想踩第二次的坑最后分享几条当时花代价换来的教训。第一抓包文件别贪大。线上 tcpdump 记得加-s 96和文件轮转否则分析时 Wireshark 会卡到你怀疑人生。第二Wireshark 显示“校验和错误/乱序/重传”时先怀疑观测手段再做网络论断。第三改参数一次只改一个改完立刻回到包里验证效果。我中间犯过的错就是一次性改了 somaxconn、keepalive 和应用超时结果哪个起作用了完全分不清只好回滚重来。现在让我再遇到类似问题我不会再去死磕网络设备了。我会先问一句这些连接是复用的还是用完就丢的这一句话常常比抓包更早指出方向。回到开头那串 W它在我眼里早就不是乱敲的字符而是一条提醒——网络问题从来不会凭空出现它只是用波浪线一样的方式把藏在上层代码里的问题画给了你看。