上篇把网络障碍诊断的前两步——物理/链路层和网络层的思路讲完了。如果说链路和网络层解决的是“路通不通”,那传输层和应用层服务解决的问题就是“门开不开、屋里的人干活不干活”。这篇把三步法的下半部分补完:传输层怎么诊断、应用层服务怎么排障,最后给出一套能直接照着用的排障流程总结。内容偏实战,尽量少讲理论,主要讲我在生产环境里踩过的坑和验证过的手段。
熟悉我排障习惯的朋友都知道,我很少一上来就抓包,也不会一上来就重启服务。诊断网络障碍最重要的是先分层、再取证、最后定位。物理层看链路,网络层看路由和连通性,传输层看建连质量和可靠性,应用层看服务行为和协议响应。每一层都有自己典型的故障特征,也有对应的工具和方法。这篇文章我打算先讲传输层的三个高频故障模式,再讲应用层服务里最容易误判的几个场景,最后把整个过程收拢成一套可复用的流程模板。
1. 传输层排障:先搞清“连不上”和“连上但慢”的区别
传输层最核心的一件事就是“能不能建立连接、连接质量怎么样”。TCP是有状态的,三次握手、重传、拥塞控制都在这一层起作用;UDP虽然没有连接状态,但丢包、乱序、缓冲区溢出同样会造成业务异常。很多人遇到网络问题就急着抓包,但抓包之前应该先问自己一个问题:这个问题到底发生在连接建立阶段,还是连接建立之后的数据传输阶段?这两个阶段的排查思路完全不一样。
1.1 判断问题发生在哪个阶段
我常用的判断方法是:先划边界,再取证据。首先从客户端做一个简单的连通性测试,比如 nc -vz -w 3 <server_ip> <port> 或者 telnet <server_ip> <port>。如果端口完全不通,说明问题大概率在TCP连接建立之前;如果端口能通但业务请求超时,那就要往应用层和传输质量方向查。
这里有个容易被忽略的点:ping通并不代表网络层没问题,更不代表传输层没问题。ICMP和TCP走的可能是不同的路径,远程网络也可能对ICMP做了特殊处理。我曾经遇到一台服务器ping延迟正常,但TCP建连就是超时,后来发现是中间防火墙只允许ICMP而丢弃了部分SYN包。所以“ping通”只能算一个弱证据,真正要确认传输层是否正常,必须以TCP/UDP的实际连接测试为准。
1.2 tcpdump怎么看三次握手和重传
抓包是传输层排查最重要的手段,但很多新手抓了一堆包不知道看什么。我的建议是先看三次握手是否完成:客户端发SYN,服务端回SYN-ACK,客户端再回ACK,一个正常的TCP连接三包就能建立。如果客户端只发出SYN就再也没有后续,那说明SYN丢了或者被中间设备拦截;如果服务端回了SYN-ACK但客户端没有继续回ACK,问题可能出在客户端一侧的回包路径上。
重传是另一个关键信号。TCP的重传机制是为了对抗丢包,但重传率过高就说明链路质量有问题。抓包时可以用 tcpdump -i any tcp port 443 观察,当出现大量 TCP Retransmission 时,尤其要关注重传间隔。正常的TCP重传间隔会依次翻倍,比如0.2秒、0.4秒、0.8秒;如果重传间隔非常不规律,甚至出现大量快速重传,可能是中间设备的队列丢包或者链路拥塞导致的。
这里必须提醒一个坑:网卡offload特性(TSO/GRO)可能会让你在抓包时看到超过MTU的大包,或者看到接收端抓到超大包。这并不一定代表网络路径真的传了这么大的包,可能只是抓包工具看到了本机网卡offload之前/之后的数据。排查时如果发现包大小异常,先用 ethtool -k eth0 查看tso、gso、gro状态,必要时 ethtool -K eth0 tso off gro off 关闭后再抓包对比,否则很容易误判。
1.3 TCP连接失败的具体模式
TCP连接建立失败,最常见的有三种模式,我一个个说。
第一种是SYN发出后没有SYN-ACK,客户端反复重传最终超时。这种模式的可能性很多:服务端端口没监听、防火墙直接丢弃SYN、中间设备丢包、连接跟踪表满、半连接队列满。排查顺序我建议先看服务端 ss -lntp 确认端口确实在LISTEN,再看两端抓包确认SYN是否真的到达服务端,最后查防火墙和连接跟踪状态。
第二种是三次握手都完成了,但连接刚建立就收到RST。这个问题往往不是网络层的锅,而是服务端应用层主动拒绝,比如后端程序做了来源IP白名单校验、防火墙配置了reject规则、或者两台机器IP冲突导致ARP表混乱。遇到RST不要急着调内核参数,先看服务端日志和程序配置。
第三种是连接能建立但明显很慢,比如从客户端到服务端延迟正常,但每次建连都要好几秒。这种情况优先怀疑SYN队列和accept队列溢出。ss -lntp 能看到 Send-Q 和 Recv-Q,对于LISTEN状态的socket来说,Send-Q表示全连接队列的上限,Recv-Q表示当前正在等待应用accept的连接数。如果Recv-Q长期接近Send-Q甚至超过上限,说明应用处理连接的速度跟不上,需要调整应用的backlog参数、net.core.somaxconn,或者看看应用进程是不是卡在某个阻塞操作上。
1.4 UDP的特殊排查套路
UDP没有握手也没有重传,排障会比TCP抽象很多。我的建议是尽量绕开“猜”,直接用工具测。DNS是最典型的UDP服务,dig 出现超时或SERVFAIL时,先确认本机配置的DNS服务器能不能通,再用 dig +trace 看每一级解析耗时。如果UDP查询丢包率很高,可以试试 dig +tcp 强制走TCP查询,如果TCP正常而UDP异常,大概率是中间链路对UDP不友好,或者UDP缓冲区太小。
测UDP链路质量我习惯用 iperf3 -u -b 100M -t 30 -c <server_ip>,它会直接告诉你丢包率、抖动和吞吐。注意这类测试一定要避开业务高峰期,UDP压测是真的会打满带宽的,我见过有人直接在业务链路上一通猛测,结果把正常业务全打挂了。测之前先在客户端和服务端把抓包开起来,测完一起分析,这样才能区分丢包是发生在哪个方向。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 应用层服务排障:端口在监听,不代表服务真的可用
传输层确认没问题之后,问题范围就缩小到应用层了。应用层排障最误导人的一句话就是“端口明明开着,为什么还是访问不了”。端口LISTEN只能说明socket存在,并不能说明进程健康、业务可用、响应及时。很多故障表面上是网络问题,根子却在应用层:服务卡死、连接池耗尽、配置错误、健康检查失败。
2.1 确认服务进程和监听地址
第一步永远是看清楚监听状态。用 ss -lntp 查看“Local Address:Port”,如果显示 127.0.0.1:8080 或者 ::1:8080,那就说明服务只监听了回环地址,外部怎么都连不上。这种情况在Redis、MySQL、一些内部管控服务上非常常见,安全上没问题,但如果你在远程排查时就容易被“明明启动了却连不上”给绕进去。
第二步是确认进程真的活着。ss -lntp 里能看到pid和进程名,再用 systemctl status <service> 查看服务状态。我遇到过服务进程还在,但已经处于D状态(不可中断睡眠),比如NFS卡住、磁盘IO卡住,socket虽然还在但不能正常处理请求。这种状态表面看端口是通的,实际业务已经瘫痪,必须结合 ps -eo pid,stat,wchan 和系统负载来判断。
还有一种很典型的情况:应用配置文件和实际监听信息对不上。比如你改完Nginx配置 listen 8080,但load balancer还在连旧端口;或者服务启动失败报“Address already in use”,你以为端口被占用,实际是另一个残留进程占着。此时 ss -lntp 是唯一可靠的判断依据。
2.2 端口通不等于业务通的典型场景
我最常说的一句话是:先搞清楚是谁在测试连通性。如果你直接在服务器本机执行 curl http://127.0.0.1/health,通过不代表外部客户端可以访问。因为中间还有防火墙、安全组、负载均衡、反向代理这些环节。
举两个高频场景。第一个是云服务器。很多云平台的网络ACL和安全组是分开控制的,ping通了但端口不通是常规操作。遇到这类问题先去云控制台看安全组规则,而不是在服务器上折腾iptables,你折腾半天的本地规则可能完全没生效。第二个是反向代理架构。Nginx本机访问8080端口正常,但用户通过域名访问返回502,问题几乎必然在Nginx和后端服务之间。这时候要看Nginx的error.log里有没有 connect() failed 的记录,再确认upstream配置的IP和端口是不是真的在监听。
还有一个场景值得单独说:健康检查。负载均衡会定期去检查后端节点的健康状态,检查方式有可能是TCP端口,也有可能是HTTP路径。如果后端的健康检查接口依赖数据库或缓存,而后端数据库连接池满,健康检查就会返回500,导致LB误把正常节点摘掉。这种故障在服务端日志里往往能看到大量的“too many connections”或连接超时,但客户端症状却是“服务时好时坏”。
2.3 HTTP服务诊断从curl -v开始
排查Web类问题,我的标准步骤是先用 curl -v 看连接过程。curl -v 会明确告诉你是否完成了TCP连接、是否发送了HTTP请求、等待响应花了多长时间。如果连接正常但一直卡在“Waiting for response”,那就是应用处理慢,和后端网络没关系;如果连接直接超时,才需要考虑网络层面的问题。
想更精确定位耗时环节,可以用 curl -w 输出时间分解:
bash复制curl -o /dev/null -s -w 'DNS:%{time_namelookup}s TCP:%{time_connect}s 首字节:%{time_starttransfer}s 总耗时:%{time_total}s\n' http://example.com/
time_connect 偏大说明TCP建连有问题,time_starttransfer 偏大说明服务端处理或首字节返回慢。如果服务端日志显示处理只要几十毫秒,但客户端 time_starttransfer 却要好几秒,那大概率是代理缓冲、负载均衡转发或者中间链路限速的问题。反向代理日志通常会出现 upstream_response_time 字段,可以用来区分是“应用处理慢”还是“代理本身慢”。
2.4 日志与系统指标对账
排查应用层问题时,我要求自己至少同时打开三份日志:应用日志、系统日志、内核日志。应用日志告诉你业务层到底发生了什么,系统日志(journalctl -xe)告诉你服务状态变化,内核日志(dmesg -T)能告诉你内核层面有没有丢包、OOM、或者连接跟踪表溢出之类的事。
这里有个常见误区:很多人只看应用日志,发现没有报错就断定“服务是正常的”。但实际上很多故障发生在底层,比如Kubernetes Pod被OOM杀掉重启、systemd配置了 Restart=always 并由它自动拉起,表面上服务一直活着,但业务已经中断过。排障时必须把时间轴对起来:什么时刻出现第一条客户端报错,什么时刻服务重启,什么时刻内核开始报OOM或nf_conntrack丢包,前后顺序一旦清楚,定位会快得多。
3. 三步法收拢:从临时救火到可复用的排障流程
这篇文章虽然叫三步法,但我前面讲的很多经验其实零散。真正的价值在于把它们收拢成一套可以照着执行的流程,不然遇到新故障还是靠拍脑袋。我自己在实践里,把排障流程抽象成三个动作:划边界、取证据、二分定位。听起来简单,实际执行时非常考验自制力,因为人总会忍不住跳过取证直接猜。
3.1 三层边界法回顾
第一步划边界:从用户端开始逐层试探。先ping,确认网络层通不通;再用nc或telnet,确认传输层通不通;最后用curl或客户端调用,确认应用层通不通。这一步的目的是把故障范围限制在一层里,而不是同时在所有层里打转。
第二步取证据:完成边界划分后,围绕疑似故障层收集数据。系统指标、应用日志、内核日志、抓包文件,都属于证据。我给自己定了一个硬性要求:没有同时拿到两个端点的证据之前,不下任何结论。比如客户端抓包显示SYN重传,服务端抓包却没看到这个SYN,那结论只能是中间路径丢包,而不是服务端有问题。只有一端的证据,永远无法区分“包没到”和“包到了但没处理”。
第三步二分定位:做一次“中间切割”。如果客户端和服务端之间还有负载均衡、防火墙、网关,优先判断问题出在链路的哪一段。一个实用操作是:分别在客户端、中间节点、服务端三处使用同样的连通性测试,观察哪一段开始出现异常。哪一段异常,问题就缩小到哪一段。这套方法本质上就是二分查找思想在网络排障里的应用。
3.2 可以直接抄的排障命令模板
很多朋友问我要“排障命令大全”,其实真正高价值的不是命令多,而是知道在哪个阶段跑哪些命令。我整理了一份自己常用的模板,按层分类。
网络层和信息收集:
bash复制ping -c 10 <server_ip>
traceroute -n -T -p 443 <server_ip>
ip addr show ip route show
ss -s
uptime && free -m && df -h
dmesg -T | tail -50
传输层:
bash复制ss -tan | awk '{print $1}' | sort | uniq -c
ss -lntp
nc -vz -w 3 <server_ip> <port>
iperf3 -c <server_ip> -t 30
tcpdump -ni any host <server_ip> and port 443 -w /tmp/443.pcap
应用层:
bash复制systemctl status <service>
journalctl -u <service> --since "-30 min" --no-pager
curl -v http://127.0.0.1/health
curl -o /dev/null -s -w 'TCP:%{time_connect}s 首字节:%{time_starttransfer}s 总耗时:%{time_total}s\n' http://127.0.0.1/health
lsof -i :8080
这些命令你不需要一次全跑,但排障时如果手里没有这些数据,后面每做一步判断都很虚。尤其要养成抓包就保存文件的习惯,tcpdump -w 落盘,等出问题时再分析;直接在屏幕上看滚动输出,很容易漏掉关键包。
3.3 实战案例:一次“登录偶发失败”的完整排障记录
我拿一个印象很深的案例来演示这套流程。某天上午十点开始,业务方反馈系统登录偶发失败,用户在页面上多刷几次偶尔能登录成功,但过一会儿又失败。客户端报错是“网络连接超时”。
我先画边界:从办公网ping应用服务器,延迟正常、无丢包;telnet应用服务器8080端口,第一次连接成功,第二次就卡住超时。这说明网络层是通的,但TCP建连不稳定,问题集中在传输层或路径上。
接着取证据:先在客户端抓包,运气不错,抓到了卡住时的SYN重传。SYN连续发出三次,第一次没有SYN-ACK,之后隔了0.2秒重传,又隔0.4秒重传,始终没有回应。再到服务端抓包,诡异的是服务端网卡上根本没有收到这些SYN。那问题必然出在中间路径,而不是应用服务器自身。
顺着中间路径查,这台应用服务器前面还挂了一台防火墙兼负载均衡设备。登录防火墙管理端看会话状态,发现连接跟踪表已经满了,日志里持续刷 nf_conntrack: table full, dropping packet。由于连接跟踪表满,新到达的SYN包直接被丢弃,客户端TCP重传也救不回来。
定位到根因后,临时清理了一批无效会话并调大了连接跟踪表上限,服务马上恢复。但这个操作只能救急,根本原因是业务侧的短连接太多:应用每做一次数据库查询都新建连接,查询结束就关闭,大量连接处于TIME_WAIT状态,把连接跟踪表塞爆了。长期修复是让应用启用连接池和HTTP keepalive,减少短连接,同时给连接跟踪表加监控,在达到80%水位时提前告警。
这个案例最有意思的地方在于:表面是网络超时,实际根子在应用层的连接使用方式。如果当时不看中间设备的连接跟踪表,而去服务器上调TCP参数或者重启服务,大概率是白忙活。这也是为什么要坚持“划边界、取证据、二分定位”的顺序。
4. 常见问题速查与实战心得
更多的经验,是在一次次误判里换来的。这一节我整理了一张速查表,同时也想分享一些文档里不会写、但真实排障中非常关键的细节。
4.1 典型症状、误判与处理对照表
| 症状 | 可能的根因 | 常被误判的方向 | 排查手段 |
|---|---|---|---|
| ping通,telnet端口超时 | 半连接队列满 / 连接跟踪表满 / 防火墙drop | 以为是服务没启动 | ss -lnt 看Recv-Q、dmesg -T、查nf_conntrack |
| TCP不断收到RST | 服务端主动拒绝 / 防火墙reject / IP冲突 | 以为是网络链路故障 | 两端抓包确认RST来源、查应用日志和ARP表 |
| 重传率高、传输速度慢 | 链路丢包 / 带宽瓶颈 / 网卡驱动异常 | 盲目调TCP拥塞控制 | tcpdump 统计重传、iperf3 -c、ethtool -S eth0 |
| 服务监听0.0.0.0但外部不通 | 云安全组 / 本地防火墙 | 在服务器上调内核参数 | 查看云控制台安全组、iptables -L -n、nft list ruleset |
| 大量TIME_WAIT | 短连接过多 / 应用未开启连接复用 | 以为TIME_WAIT本身就是故障 | ss -tan state time-wait | wc -l、应用连接池配置 |
| curl卡在waiting但日志很快 | 代理缓冲 / 负载均衡转发慢 / 链路限速 | 以为应用处理慢 | curl -w、对比应用access log与LB日志 |
| DNS时好时坏 | systemd-resolved缓存 / 上游UDP丢包 | 拼命刷新本地DNS缓存 | dig +trace、resolvectl status、测试TCP DNS |
| 应用偶发超时但端口正常 | 服务线程阻塞 / 数据库连接池满 / OOM重启 | 重启服务“解决”问题 | ps -eo pid,stat,wchan、应用日志、journalctl |
表格里这几类我都见过不止一次。尤其“大量TIME_WAIT”这个,好多人一看到就急着开 tcp_tw_reuse、tcp_tw_recycle。我特别强调一句:tcp_tw_recycle 在NAT环境下会引发很严重的乱序问题,新内核已经把它移除了,你要是从老文章里抄这个参数就是在给自己挖坑。TIME_WAIT本身是TCP正常状态,关键在于减少不必要的短连接,而不是靠内核参数硬压。
4.2 那些“文档里不会写”的细节
第一件:抓包之前先确认两端时间是否同步。有人在跨机房排障时抓了两端pcap,发现同样的SYN时间差了好几分钟,根本没法对比。虽然TCP抓包可以靠seq号分析,但时间轴对不上会让排查效率大幅下降。排障前至少顺手跑一下 date,差得多了就先解决NTP问题。
第二件:iptables -L 看到的规则不一定完整。Linux上防火墙可能有iptables、nftables、firewalld多个管理入口,规则可能在不同表中。我建议直接看 iptables-save 或 nft list ruleset,免得被默认表格视图误导。新版系统上用nftables的场景越来越多,你还用老的iptables命令去看,自然看不到真实规则。
第三件:不要忽略SELinux。Nginx监听成功不代表SELinux就放行了,很多“连接被拒”其实是被SELinux的AVC策略拦下。遇到端口开着但连接异常,先跑 ausearch -m avc -ts recent 看有没有拒绝记录,别一上来就 setenforce 0,那是最后手段。
第四件:应用层的“健康”必须用业务视角验证。端口能监听、进程活着、日志没报错,这三条全满足也可能服务不可用。比如Java应用发生Full GC导致线程停顿几十秒,客户端表现为请求超时,但服务端日志可能什么都没记录。这类问题的排查,光看Linux命令已经不够了,还要熟悉应用的指标和监控。
第五件:调整参数一次只动一个。不知道你有没有遇到过这种情况:同事排障时同时改了内核参数、换了配置、重启了服务,然后问题好了,但没人知道到底是哪个操作起了作用。下次再出问题,又得从头猜。正确做法是每次只改一个变量,改完等一段时间观察效果,再决定下一步。这套纪律在复杂故障中特别重要。
4.3 排障习惯和时间线记录
排障不是记流水账,但一定要留痕。我自己的习惯是维护一个简单的排障记录,按“现象-证据-假设-验证”四段来写。现象就是用户能看到什么;证据是命令输出和日志,假设是每一步的判断;验证是操作后是否解决了问题。很多时候故障是多个因素叠加导致的,没有记录就很容易在复盘时漏掉关键一环。
如果你刚接触Linux网络排障,我建议你先从建立“分层思维”开始。遇到问题不要急着百度“某某报错怎么解决”,先想清楚它属于哪一层:是路由不通、建连失败、还是应用返回5xx?每确定一层,就把范围缩小一半,排查速度自然就上来了。这套三步法最核心的价值,不是某个命令有多神,而是让你在复杂网络环境里始终有一套稳定的路径,不靠运气排障。
