资讯动态

网络与IO问题排查实战:从WebSocket断连到系统级定位

发布时间:2026/9/16 7:19:54 来源:尧图企业网站定制
做后端服务排查时间长了你会发现一个规律大部分线上故障最后都能归结到网络或IO上。网络与IO问题排查之所以让人头疼不是理论有多深而是现象太会“伪装”。比如客户端偶尔报一句stream disconnected before completion: failed to send websocket request: io error: peer closed connection with乍一看是WebSocket框架的bug实际查到最后可能只是对端服务器文件描述符耗尽。这一章我打算把这些年在网络和IO排查上积累的实战方法完整讲一遍既给结论也给推导过程方便你下次遇到类似问题能直接按图索骥。1. 一次线上WebSocket断连排查从报错到缩小嫌疑范围1.1 那个让人误判的报错长什么样先还原一个我处理过的真实场景。某个实时推送服务客户端会不定时断开连接服务端日志里反复出现stream disconnected before completion: failed to send websocket request: io error: peer closed connection with第一次看到这个报错团队里几个人的第一反应都是“WebSocket客户端库有问题”“是不是服务端主动把连接关了”。但如果你只盯着这一行日志很容易被带偏。这个报错的字面意思是在发送WebSocket请求的过程中连接还没有完成就被断开了底层IO报告了一个错误对端关闭了连接。它并没有告诉你“是谁先关的”也没有告诉你“为什么关”。所以真正要做的不是搜框架的issue而是先回答三个问题连接是什么时候断的断在哪一跳断开之前发生了什么1.2 排查不是从猜开始而是从保留现场开始很多人遇到问题第一反应是“重启大法”或者“先改个配置试试”这在大规模分布式环境下风险很高。连接断开这种问题如果不保留现场日志一旦被滚动覆盖就只能靠猜了。我的习惯是接到故障后先做四件事把客户端、服务端、网关或负载均衡的日志时间窗口对齐按分钟维度拉出来。抓取当前所有相关连接的状态用ss -antop查看端到端的连接是否存在、处于什么状态。如果条件允许在可疑节点同时抓包保留TCP层面的原始证据。记录下当时的监控曲线包括CPU、内存、连接数、文件描述符使用率、网络重传统计。这四步做完即使还没定位到根因手里也已经有了可以反复推敲的证据链。很多临时“看起来能解释”的猜测一放到完整证据链里就不攻自破了。1.3 五类常见原因和排除方法根据那一次排查以及后来接触到的类似问题WebSocket或长连接被“突然断开”的原因通常集中在五类可能原因典型特征快速判断方式服务端进程崩溃或被OOM杀死服务端日志中断进程消失监控有内存突变检查进程存活时间看内核日志dmesg文件描述符耗尽服务端日志报Too many open files连接数接近上限执行ss -s、cat /proc/pid/fd计数空闲超时被中间设备断开断开时间点比较规律比如固定空闲N分钟看连接空闲时长查网关超时配置网络中间设备RST抓包能看到RST但服务端应用层没有关闭操作tcpdump抓包分析RST来源客户端主动断开客户端日志有主动close但服务端感知滞后查看客户端退出码和调用栈那次实际定位结果是服务端某个实例的文件描述符上限设得不够平时连接少看不出来一旦瞬时并发上来fd耗尽后内核直接拒绝新连接同时部分已有连接也被迫关闭。客户端那边看到的就是peer closed connection。这个排查链路的启示是连接断开本身只是结果不是原因必须一层层看链路状态。2. 网络层排查实战把“通不通”和“好不好”分开看2.1 先别急着抓包用最基本的命令确认连通性网络问题排查有个常见误区一上来就tcpdump抓了一大堆包然后被里面密密麻麻的TCP字段淹没。我的习惯是先用最小成本把范围切出来。第一步是ping。很多人觉得ping太基础但它能快速告诉你两件事目标主机是否可达以及网络延迟的大致水平。注意ping用的是ICMP协议它能通不代表TCP端口能通但如果你连ping都不通那问题大概率出在网络路由或防火墙层面TCP应用层的排查可以先放一放。第二步是端口连通性测试。我常用telnet ip port或者nc -vz ip port。这一步能判断目标端口是否在监听、防火墙是否放行。如果telnet能连上说明TCP三次握手是成功的问题可能出在应用层协议交互上。第三步是看路由。跨机房、跨地域访问慢可以用mtr或traceroute看每一跳的延迟和丢包。很多时候你会发现延迟都花在某一个中间节点上这时候再优化本地服务代码是没有意义的。2.2 抓包之前先想清楚要看什么抓包不是把包抓下来就完事而是要带着问题去看。常见的网络层问题无非几类TCP握手慢、数据重传、乱序、窗口缩小、连接被RST。用tcpdump抓包时我通常只抓目标端口和特定IP避免产生大量无用文件tcpdump -i eth0 -nn -s 0 host 10.0.0.5 and tcp port 8080 -w /tmp/trace.pcap抓完用Wireshark打开第一时间看TCP的Conversation和Flow Graph。如果有大量重传说明网络链路存在丢包如果有乱序说明路径上有多路径负载均衡或链路质量问题。有一个容易被忽略的点RST包到底是谁发的。客户端日志显示连接被断开但抓包发现RST的源IP是中间的负载均衡设备那就说明服务端可能根本没收到断开指令问题在中间链路。反过来如果RST源IP是服务端那就要去查服务端应用或操作系统为什么主动拒绝。2.3 MTU、防火墙和连接跟踪隐藏的网络坑除了常见丢包和延迟还有几个隐藏比较深的点。MTU 问题。如果服务端和客户端之间隔着隧道或虚拟网络MTU不一致会导致大包被丢弃而小包正常。典型现象是ping小包正常但是传输超过一定大小的数据就卡住或断开。排查方式是用ping -M do -s size测试不同包大小逐步找到临界值。防火墙连接跟踪。很多云环境或自建iptables环境会开启conntrack当连接跟踪表满的时候新连接会被丢弃甚至已有连接也会异常。这个坑的表现很像“间歇性网络故障”但实际是系统层的连接跟踪表满了。用conntrack -S可以查看统计一旦发现insert_failed或drop在增长基本就是它了。网络测速也要注意。在线测速和实际业务表现经常对不上因为测速往往只测带宽不测延迟和丢包。真实场景里很多IO问题其实是网络质量波动引起的比如跨运营商线路高峰期丢包率上升TCP重传导致请求变慢。这时候应用层表现的是IO超时但根因在网络链路不是你代码写得不对。3. IO层排查实战fd、缓冲区、线程模型一个都不能少3.1 文件描述符耗尽一个被低估的故障源回到IO排查。如果说网络层看的是链路那IO层看的就是进程与内核之间的交互。IO问题里最常见也最容易被误判的是文件描述符耗尽。Linux下一切皆文件socket连接也占用文件描述符。当进程的fd达到上限后新的连接无法建立已有的读写也可能异常。判断方法很简单# 查看当前进程fd占用数 ls /proc/pid/fd | wc -l # 查看进程fd上限 cat /proc/pid/limits | grep open files如果fd占用数接近上限基本可以确认是这个原因。但有个细节fd会以socket:形式占用单纯 count fd 文件数不够直观。用ss -antop能看到每个socket对应的进程和fd号结合统计脚本可以快速列出哪个进程最吃fd。为什么fd会突然涨上去大多数时候是连接泄漏代码里创建了连接但异常分支没有关闭。这类问题在长连接场景下尤其隐蔽平时看监控连接数平稳一旦某个上游服务变慢所有线程都卡在创建连接的路径上fd就瞬间被打满。3.2 Socket缓冲区与read/write的阻塞陷阱很多IO性能问题本质上是缓冲区不匹配导致的背压。服务器向客户端写数据时如果客户端不读服务端的发送缓冲区会慢慢填满填满后write或send就会阻塞。这个时候从应用层看线程卡在IO上CPU不高但接口响应极慢。一个典型的排查案例服务A往服务B发数据B消费速度变慢A的发送缓冲区被占满导致A的工作线程全部阻塞。表面上看是A“IO性能下降”实际是B的消费能力成了瓶颈。碰到这类问题可以通过ss -antop查看socket的发送接收队列ss -antop | grep -E Send-Q|Recv-Q如果Send-Q长期不为0说明对端读取速度跟不上。如果Recv-Q长期不为0说明本进程读取速度跟不上。这个判断非常直接能帮你快速定位阻塞在哪一端。3.3 IO模型选型别再什么都用阻塞IO排查IO问题的时候绕不开的是IO模型。很多老服务仍在使用传统的阻塞IO一个线程处理一个连接连接数一上来线程数就爆炸线程上下文切换开销直接把CPU拖垮。我在代码评审时经常看到这样的场景服务依赖的是一个低延迟的内网接口连接数不大用阻塞IO完全没问题。但如果是对外提供的长连接服务或者需要支撑高并发网关就必须考虑非阻塞IO、IO多路复用select/poll/epoll或异步IO。排查时怎么判断是不是模型问题看两个指标一是线程数是否随着连接数线性增长二是CPU的sys时间占比是否过高。如果线程数几千、sys时间居高不下大概率是线程切换和系统调用太频繁。此时即使不重构代码也应该通过调整线程池大小、减少阻塞等待来缓解。Java生态里从BIO切换到NIO或Netty往往能带来数量级的提升但这里有个坑模型切换后代码里的线程模型、超时处理、背压策略都要重新设计。如果只是简单替换框架老代码里的阻塞逻辑还在问题反而更隐蔽。4. 系统级IO性能下降的定位套路4.1 先用系统工具圈定是磁盘IO还是网络IO“IO性能明显下降了”这句话在工单里出现的频率极高但它太模糊。IO在Linux系统里至少可以分两类磁盘IO和网络IO。定位第一步是分清你遇到的到底是哪一种。我惯用的指令序列是# 看系统整体IO情况 iostat -x 1 5 # 看进程级IO pidstat -d 1 5 # 看内存和交换情况 vmstat 1 5 # 看网络栈统计 netstat -siostat里重点看%util、rkb/s、wkb/s、await。如果%util接近100%说明磁盘设备已经接近饱和。但要小心%util高不一定意味着性能差还要看await和队列长度。如果await也很高说明IO请求在排队磁盘真的忙不过来。如果磁盘IO一切正常但接口还是慢那就把重点切到网络。netstat -s里的重传、丢包、timeout计数能帮你判断是不是网络链路质量在恶化。4.2 应用层的IO瓶颈线程池、队列和超时系统工具只能告诉你内核和硬件层面发生了什么应用层的IO瓶颈往往需要结合业务指标来观察。举个实际例子一个异步处理任务消息队列里的消息积压越来越多但消费端的CPU、磁盘IO、网络IO看着都不高。这时候要怀疑消费线程是不是大部分时间都在等待外部调用的返回。可以看线程的堆栈jstack pid thread_dump.txt然后统计有多少线程处于WAITING或BLOCKED状态。如果大量线程阻塞在同一个第三方客户端调用上那问题就变成了“外部依赖变慢导致消费能力下降”。这跟磁盘或网络IO都没有关系根因是线程池被外部慢调用占满。还有一种常见情况连接池配置不合理。数据库连接池、HTTP连接池、Redis连接池如果最大连接数设置过小在高并发下请求会排队等待获取连接。观察到的现象同样是IO慢但实际上是在等连接。这时候去调大连接池往往可以快速缓解但要记得看被等待的下游是否能承受更多连接。4.3 从监控指标反推IO瓶颈如果你有比较完善的监控系统我建议把IO相关的指标拆成三层层级核心指标说明硬件层磁盘await、网络重传率、网卡丢包系统资源是否饱和内核层文件描述符使用率、socket队列长度、context switch系统调用和内核瓶颈应用层接口P99耗时、线程池活跃数、队列积压业务侧的响应能力很多IO性能下降不是单一原因而是多层叠加。比如下游服务变慢导致线程阻塞线程阻塞导致socket接收队列堆积接收队列堆积又导致上游发送窗口缩减最后表现为端到端延迟飙升。如果只盯着某一层的指标很容易误判。我自己的习惯是每次排查都画一条时间线把从“用户发起请求”到“拿到响应”的每个环节耗时列出来。可以不用全链路追踪系统哪怕在关键路径上打几个时间戳也能快速找到耗时最长的节点。这一步做好IO问题基本能定位到具体进程和具体调用。5. 高频网络与IO错误快查与排查动作清单5.1 错误信息与实际根因对照下面这张表是我这些年遇到的高频错误信息与常见根因的对照能帮你在收到类似告警时快速建立预判。错误信息或现象最可能的根因第一排查动作Too many open files文件描述符耗尽ulimit -n和/proc/pid/limitsConnection reset by peer对端应用或内核主动发RST抓包看RST来源Broken pipe对端已关闭连接本端还在写检查对端连接状态Operation timed out网络链路或对端无响应mtr、抓包、查对端监控connect timeout路由不通、防火墙丢包、服务过载telnet/nc测试stream disconnected before completion连接在消息发送过程中被断开看客户端、服务端、中间链路日志Read timed out对端没有在预期时间内返回数据看下游依赖是否变慢这张表不是给你“按图索骥抄答案”的因为同一个错误可能对应多个根因。更合理的用法是拿错误信息当线索按上面的方法逐步验证而不是看一眼就下结论。5.2 一套我常用的现场排查命令组合工欲善其事必先利其器。我把最常用的命令整理成一个组合遇到网络或IO问题我会按顺序执行# 1. 看系统负载和运行时间 uptime # 2. 看TCP连接状态汇总 ss -s # 3. 看指定端口的连接详情 ss -antop | grep port # 4. 看磁盘IO iostat -x 1 5 # 5. 看进程级别IO pidstat -d 1 5 # 6. 看网络接口丢包和错误 ip -s link # 7. 看连接跟踪表 conntrack -S # 8. 如果服务是Java抓线程栈 jstack pid jstack.log这套命令组合的优点是覆盖面广执行成本低。不管是网络问题还是IO问题都能先拿到一份“现场快照”。快照到手后再根据异常指标决定要不要抓包、要不要打开全链路追踪。5.3 复盘时的几条实战经验最后分享几个我在多次排查后沉淀下来的习惯。第一个习惯是任何网络或IO告警都不要只看单机指标。很多问题是上下游连锁反应比如上游超时重试导致下游连接数暴涨单看下游某台机器会以为是自身问题实际上是被上游打崩的。所以排查时一定要把调用链上下游的指标放在一起看。第二个习惯是线上变更记录要随时能查到。网络和IO问题经常是某个配置变更引入的比如改了内核参数、调了负载均衡超时时间、升级了SDK版本。如果变更记录不清晰排查到根因可能需要多花几个小时。我现在会要求团队所有网络参数、线程池参数、连接池参数的变更都走配置管理至少能在分钟级内回溯。第三个习惯是不要忽略基础监控盲区。很多服务只监控了CPU和内存但文件描述符使用率、socket队列长度、TCP重传率、线程阻塞数这些指标没有监控。等到出问题的时候历史数据缺失很难判断是突然恶化还是缓慢积累。建议至少把ss -s、iostat -x、fd使用率这几个指标接入监控设置合理的告警阈值。网络与IO问题排查说到底是一场“证据链”的较量。你收集的信息越完整对链路每一层的理解越准确定位根因的速度就越快。别指望有一条命令能解决所有问题真正值钱的是你面对纷乱现象时能始终保持“分层拆解、逐步排除”的思路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价