西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → 抓了200个包才发现:连接偶发超时藏在TCP重传里

抓了200个包才发现:连接偶发超时藏在TCP重传里

相关软件相关文章发表评论 来源:本站整理时间:2026/11/2 21:04:04字体大小:A-A+

  • 类型:站长工具大小:593KB语言:中文 评分:10.0
  • 标签:
立即下载

上周有个客户半夜给我发消息,说他们的一个接口"时好时坏",同一个请求,同样的参数,服务端日志里明明写着 200 OK,返回体也完整,可客户端那边就是隔三差五地报超时。我第一反应是让他们把接口路径和报错贴过来,第二反应是问时间点因为这种玄学问题,不给出精确到分钟的时间线,根本没法往下查。

他们给的回复是"大部分时候都正常,一天出现那么几次"。我心里咯噔一下:服务端 200、客户端超时、频率不高,这三个条件凑在一起,答案大概率不在应用代码里,而在链路中间。

接口时好时坏、服务端却返回200,怎么快速判断是不是网络链路的问题?深蓝群Ping(多服务器断线与网络质量检测)(站长工具,V3.5.2绿色版)可以先把"服务端自己通"和"从外面过去通"这两件事分开验证,它支持同时对多台服务器持续探测,把丢包率和断线次数的时间轴拉出来对比。很多所谓的"应用偶发故障",用这种连续观测一跑,曲线形状立刻就不一样了。

接下来的一个下午,我在客户端机器上抓了将近两个小时包,一共两百多个会话,罪魁祸首终于浮出水面:不是应用超时,是 TCP 层的重传叠加了一个被忽略的 keepalive 超时。过程记录在这里,权当一份我自己下次能照着做的排查清单。

一、先别急着抓包:把"谁慢"这口锅分清楚

抓包之前有个很朴素但很多人跳过的步骤:确认到底是服务端处理慢,还是响应没回到客户端。最直接的办法是同时打两个时间戳服务端记录请求开始和响应结束,客户端记录请求发出和收到响应,两边一减,差值就出来了。

如果服务端内部的耗时本身就接近那个超时阈值(比如接口设置的是 3 秒,服务端自己就花了 2.8 秒),那问题在代码和数据库,跟链路一点关系没有,抓包抓到手软也看不出东西。反过来,如果服务端内部只花了 12 毫秒,而客户端看到的却是 3 秒超时,那这 2.988 秒消耗在路上,抓包才有意义。

我这次就是靠这一步把方向定下来的:服务端 P99 耗时 15 毫秒,客户端超时阈值 3 秒。差值太大了,不可能是应用。

Linux服务器在机房机柜中运行抓包排查网络请求超时

二、抓包第一件事:别抓错网卡

服务器上抓包有个很常见的坑:机器上有多块网卡,比如公网网卡、内网网卡、虚拟网卡、docker 的网桥。你随手 tcpdump eth0,结果抓的是本地回环或者虚拟网卡上的流量,看半天什么都没有,或者看的根本不是客户流量。

我的做法是先确认流量到底从哪块网卡进出,再决定抓哪个接口。简单粗暴但有效的办法是先在应用侧打一个标记,比如在请求头里塞一个唯一值,然后抓所有网卡再过滤这个值;或者直接看路由表,destination 为 0.0.0.0/0 那条走的是哪块网卡,就抓哪块。容器环境更要注意,宿主机上抓到的通常是 veth 对,容器内抓才是容器视角的 eth0,两个视角看到的东西不一定一样。

另外抓包位置也很关键。如果你的服务前面挂了一层 Nginx,抓在 Nginx 前面还是后面,看到的连接行为完全不同。我这次是应用直连,没这层,就直接在业务网卡上抓。

命令本身不复杂,指定网卡、端口、过滤条件,再加 -w 存成 pcap 文件方便反复分析:

tcpdump -i eth0 -nn -s 0 -w /tmp/prod.pcap 'tcp port 8080 and host 203.0.113.24'

几个参数值得说明一下。-nn 是让 IP 和端口都以数字显示,不然反查域名会拖慢速度;-s 0 是抓完整包,不要截断头部;-w 存文件而不是打到屏幕上,因为屏幕上刷过去的包你根本来不及看。

运维工程师在办公桌前用终端进行tcpdump抓包分析

三、在包里我看到了什么

打开 pcap 的第一件事不是逐包看,而是看统计。我用的是 wireshark 的 Conversation 视图,先看 TCP 那个协议标签下的 Conversations 表,按 Bytes 和 Packets 排一遍序,找那种"字节数巨大但持续时间也很长"的会话这类会话往往就是问题所在。

这次我盯上的,是几条从客户端到服务端、持续时间刚好卡在 3 秒左右的会话。点进去看单个 TCP 流,重传的红旗立刻就插起来了:同一个 seq 号的报文出现了两次、三次甚至更多次,每次间隔都在几百毫秒。

机房网络机柜中交换机端口与网线,排查网络链路异常

具体我看到的现象是这样的:客户端发起请求,SYN、SYN-ACK、ACK 三次握手正常,客户端发出请求后服务端确实回了 ACK 和数据,说明服务端处理了。但客户端在等后续数据的时候,对那个丢失的报文发起了重传,而重传的报文到达服务端后,服务端回的是重复 ACK 而不是新数据。

这一串动作说明得很清楚:数据包在链路某个环节被丢了。更让我在意的是重传的次数不是丢一次两次,是反复丢了好几次,每次都要等一个 RTO 超时。这次抓包我数了一下,最夸张的一条流里重传了 4 次,累计白等的时间超过了 2 秒,这和客户端 3 秒超时的现象完全对得上。

顺着这条线继续查,问题指向了 MTU 和分片。链路上跑的是 PPPoE 或者某种隧道,MTU 被限制在 1492 甚至更小,而我的应用往一个包里塞了一个稍大的 TCP 段,中间设备按大包转发,结果小包(ACK、重传)能过,大包一去就 fragmentation 掉甚至被丢。这是个经典现象:平时小流量一切正常,数据量一大或链路一抖就出问题。

数据包在网络节点间传输与重传的抽象可视化

四、keepalive:长连接每小时断一次的另一个凶手

顺手还发现第二个问题,和抓包不完全是一回事,但症状很像。客户那边另一个投诉是长连接每隔一小时左右断一次,重连又好了。抓包看 TCP 流,前一条连接是正常发 FIN 关闭的,没有任何异常标志位,RST 也没有。

这种"干干净净"的断开一般是中间的设备主动掐的NAT 表项超时、负载均衡的空闲连接回收、或者云厂商的安全组/网关有空闲超时设置。默认的 NAT UDP/TCP 空闲超时常见是 300 秒到 1800 秒不等,具体看是谁在做 NAT。

我的处理办法是打开 TCP keepalive 并调小它的探测间隔,让连接上的业务流量本身就能刷新中间设备的会话表:

net.ipv4.tcp_keepalive_time = 60(空闲多久后开始发探测包,默认7200秒太长)

net.ipv4.tcp_keepalive_intvl = 10(两次探测之间的间隔,默认75秒)

net.ipv4.tcp_keepalive_probes = 3(连丢几次判定连接已死,默认9次)

在应用层也一样,Nginx 的 keepalive_timeout、proxy_read_timeout、以及后端服务自己的连接池空闲回收时间,都要和这个数量级对齐,不然客户端觉得还活着、服务端已经把连接关了,一样会报莫名其妙的错。

改完 keepalive 参数之后,那条每小时断一次的连接已经连续跑了三天没断。抓包确认 keepalive 探测包确实在发,中间设备的空闲表项被刷新了。

深夜办公场景中工程师排查长连接断开问题

五、我沉淀下来的排查顺序

这次折腾完,我把自己下次会做的步骤整理成了七条,贴在工位上:

第一,先用时间戳把"服务端慢"和"网络慢"分开,别一上来就抓包。

第二,确认抓的是正确的网卡,容器环境先搞清 veth 和网桥的关系。

第三,抓包存文件,用统计视图先定位异常会话,再逐包细看,不要手工逐行读。

第四,看到重传就往丢包方向想,往 MTU 分片、往中间设备丢小包丢大包的方向想。

第五,对比客户端和服务端两边抓的包,如果服务端发了而客户端没收到,锅在中间;如果客户端压根没发出,那就是应用或者本机网络栈。

第六,长连接"干净地断"先怀疑空闲超时和中间设备回收,别急着怀疑对面服务。

第七,任何改动都先在预发环境复现同样的抓包特征再上生产,并且把抓包的时间窗口和报错时间对齐着看。

最后提醒一句,抓包是双刃剑:抓全网卡全端口很容易把敏感数据一起录进去,涉及用户信息、token、密码的包不能随便留在磁盘上。我这次抓完第一轮就做了脱敏,只保留握手头部的几个字段做分析,需要保留原始 pcap 的时候就加密存储并设定过期时间。

网络问题之所以难缠,是因为它大多数时候都是"好的",只有那 1% 的时候在坏。等你用日志去捞,往往已经捞不回来了。所以真正管用的办法,就是提前把观测手段架好连续探测的图表、随时能抓的包、以及一个能对齐时间线的日志格式。工具只是手段,能在半夜十二点把现场复现出来,才是解决问题的那把钥匙。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲