先说一个几乎所有做过线上故障处理的人都会遇到的场面:业务监控大屏飘红,研发群里有人喊“调不通了”,紧接着就有人接一句“是不是DNS挂了”,另一个人说“不对,感觉是TCP连不上”。然后,争论的人各自打开工具乱按一通,最后靠重启大法解决问题——至于到底哪里出的问题,没人说得清。
我这些年被类似的“悬案”折磨过不少次,后来慢慢总结出一套固定套路:网络故障排查,永远应该沿着“DNS解析 → 网络连通 → TCP连接 → 连接质量”这条链路一步步往下走,每一层验证通过再进入下一层。这不是什么高深理论,而是一条能大幅缩短排障时间、减少同事之间互相甩锅的实战路径。这篇文章我就把自己常用的这条排查链路完整梳理一遍,把命令、原理、典型坑位都摆出来,希望能帮你把乱七八糟的“网络不通”变成一张清晰的定位清单。适合正在自己排查服务的开发、运维、网工,也适合刚入门想建立体系化排查思路的人。
1. 为什么排障要按“DNS → TCP”的顺序来:先画一张协议栈地图
1.1 绝大多数“网络不通”的真相:故障是分层的
先把一个关键认知说清楚:你遇到的每一个“网络不通”,从来都不是一个孤立的错误,而是某一层协议栈没有按预期工作。用户访问一个网站,背后经过的链路大致是这样:浏览器先做DNS解析拿到IP,然后发起TCP连接,连接建立后才是HTTP请求的发送和响应。哪怕只是“页面打不开”这五个字,可能的原因就能列出一长串:域名解析失败、解析到了错误IP、IP不可达、路由黑洞、防火墙拦截SYN包、连接超时、建立连接后经常断、数据包丢失严重导致请求永远发不完……
如果不分层,排障就像在暗室里找一只不存在的猫。你ping了一下域名发现不通,立刻怀疑服务器宕机,结果其实是你本地DNS缓存了解析,压根没连到正确的服务器。反过来,你用IP访问发现服务明明是好的,但又说不清为什么域名访问偶尔超时。这些情况的本质,都是没有把“哪一层出的问题”先框定住。
所以我的习惯是:不管用户报什么网络故障,第一件事永远是把链路拆成三层来看——域名解析层、IP连通层、TCP传输层。每一层都有对应的验证工具和典型症状,先定位到某一层,再往细节里钻。
1.2 为什么必须先从 DNS 开始,而不是先抓 TCP 包
有人会问:既然TCP才是最基础的连接,为什么不先去抓包看握手?这里有个很实际的逻辑:TCP连接的前提是拿到目标IP和端口,而绝大多数业务场景里,你要连接的服务器是用域名表示的。如果DNS解析这一关就错了——解析超时、解析到旧IP、缓存了错误结果——你后续再怎么抓TCP包都是浪费时间,因为你可能在跟一个根本不对的地址较劲。
另外从排查成本上看,DNS解析的检查也最便宜。一条nslookup或dig命令几秒钟就能出结果,而抓包分析TCP握手少说要花几分钟,还要带着过滤器去判断SYN、SYN-ACK这些标志位。先做成本最低的验证,再进入成本更高的环节,这是排障效率的基本原则。
排障顺序也对应着依赖关系:TCP建立在IP之上,IP建立在DNS解析出的目标地址之上。自顶向下逐层验证,每过一层就排除一批可能性,剩下的范围自然越来越小。等到你确认DNS没问题、IP能通,再开始查TCP握手状态,这时候抓包看到的东西才有意义。
1.3 开工前先备好这几把“扳手”
在开始讲具体排查之前,我先把平时最常用的一套工具列出来。这套工具在Linux和Windows下基本都有对应版本,不用装什么重型软件,系统自带的能力已经够排查九成问题:
| 工具 | 作用 | 典型排查场景 |
|---|---|---|
nslookup/dig | DNS解析查询 | 域名解析不到、解析结果异常 |
ping | 测ICMP连通性 | 目标机器是否在线、IP是否可达 |
traceroute/tracert | 路由路径追踪 | 中间路由丢包、链路绕路 |
telnet/nc | 测试端口连通性 | TCP端口是否对外开放 |
curl -v | HTTP访问并输出详细过程 | 整体访问链路哪里卡住 |
ss/netstat | 查看本地连接状态 | 连接卡在SYN_SENT还是ESTABLISHED |
tcpdump | 抓包分析 | 定位握手失败、重传、丢包问题 |
这套工具不用一次性全上,而是按“DNS → IP → TCP”的顺序逐层启用。每验证完一层,记录一下结果,确认没问题再动下一层。不然工具开了一大堆,信息反而是噪音。
2. DNS 解析异常:最隐蔽也最容易误判的坑
2.1 一次域名解析要经过哪几关
域名解析看似是个一步操作,实际是一条完整的查询链。我在排障时习惯把它拆成四段:浏览器/系统缓存 → 本机hosts文件 → 系统配置的DNS服务器 → 根服务器与权威服务器。
第一段是缓存。你的浏览器、操作系统甚至路由器都会缓存DNS结果,缓存有效期由域名解析结果里的TTL值决定。很多“为什么我改完域名解析了还是不生效”的问题,根子就在缓存。Chrome输入chrome://net-internals/#dns可以查看并清空DNS缓存,Windows下用ipconfig /flushdns,Linux下根据systemd-resolved或nscd的不同缓存机制处理。
第二段是hosts文件。Windows在C:\Windows\System32\drivers\etc\hosts,Linux在/etc/hosts。某些内网环境会利用hosts做域名映射,如果里面写了一个过期的IP,即使DNS服务器上的解析是对的,实际访问还是会走hosts的旧地址。
第三段才是真正意义上的DNS查询。系统把解析请求发给配置的DNS服务器,这台服务器要么自己知道答案,要么代表你去问根域名服务器和权威服务器。第四段就是整个递归和迭代过程,通常不会出问题,一旦权威服务器响应慢,就会变成“解析时好时坏”的诡异现场。
2.2 症状识别:到底是“解析失败”还是“解析错误”
很多初学者把DNS问题的症状搞混。我举几个最常见的:
- 浏览器直接报“无法找到 DNS 地址”或“找不到服务器IP地址”。这个最直接,多半是当前用的DNS服务器根本没给出答案。
- 命令行里
ping example.com报unknown host,说明系统层解析就失败了,和浏览器报的其实是同一件事。 curl访问域名报Could not resolve host,同样是解析环节出了问题。- 更隐蔽的是“解析成功但结果不对”。比如你用
nslookup能看到A记录,返回IP却是别人家的旧地址,或者解析到内网IP但实际服务在另一个网段。这种情况不只是“有没有解析到”,还要看“解析到的对不对”。
遇到“解析失败”,优先查三个地方:本机hosts有没有写错、当前配置的DNS服务器是否可用、这台DNS服务器是不是被运营商污染或者部分域名解析不了。遇到“解析错误”,就需要对比多家DNS服务器的解析结果,看看是不是只有你用的那台有问题。
2.3 定位 DNS 问题三步法
我一般按下面三个步骤做,基本不会漏:
第一步,用系统默认DNS解析一次,看结果。nslookup example.com(Windows和Linux通用),dig example.com(Linux更推荐,输出带TTL和查询耗时)。先确认默认配置下能不能解析出来。
第二步,换一个知名公共DNS再解析一次。比如nslookup example.com 114.114.114.114或dig @223.5.5.5 example.com,看是不是换了服务器就能解析。如果默认服务器解析失败、公共DNS能成功,那问题就锁定在你当前配置的那台DNS服务器上,要么是它本身故障,要么是它到权威链路的路径有问题。
第三步,对同一域名查出来的多个结果做对比。分别查询几组不同的DNS服务器,把所有A记录列出来对比。如果公共DNS之间结果一致,但你公司内网DNS给出的结果不一样,多半是内网DNS有特殊配置或缓存过期。我遇到过不止一次,内网DNS把某域名缓存在一台已经退役的服务器上,导致业务偶发超时。
2.4 Linux 下改 DNS 的经典坑:配置总是“还原”
热搜词里有一条特别真实:“linux修改dns后重启网络+还原”。这个问题我在Ubuntu、CentOS上都踩过,根因在于不同发行版的DNS配置管理体系不一样。
很多老教程让你直接编辑/etc/resolv.conf,这在某些系统上确实立竿见影,但一到重启网络服务或者重启机器就全没了——因为你的配置被NetworkManager或systemd-resolved覆盖了。Ubuntu 22.04这类新版系统,/etc/resolv.conf通常是个软链接,指向systemd-resolved生成的文件,你改了等于白改。
正确的做法是先确认系统用哪套网络管理机制。如果是NetworkManager,用nmcli con mod改对应连接的ipv4.dns和ipv4.ignore-auto-dns;如果是netplan(Ubuntu 22.04默认用这个),就在/etc/netplan/*.yaml里修改nameservers配置,然后sudo netplan apply;如果只是临时测试,可以用resolvectl dns命令直接临时设置。
改完别忘验证:resolvectl status看当前生效的DNS,再用systemd-resolve --status(部分版本)或直接ping一个域名测试。这里还有个容易骗过自己的细节:systemd-resolved默认有缓存和DNS stub,就算你配置已经变了,也可能要等缓存失效或手动重启systemd-resolved服务才生效。
2.5 公共 DNS 怎么选:延迟、可用性、内网域名的平衡
很多人在选DNS服务器时只看“名气大”,其实这里面的取舍挺多的。国内常见的公共DNS大概有这些:
| DNS服务器 | 地址 | 特点 |
|---|---|---|
| 114 DNS | 114.114.114.114 | 老牌,用户基数大 |
| 阿里DNS | 223.5.5.5 / 223.6.6.6 | 阿里云维护,解析速度稳定 |
| 百度DNS | 180.76.76.76 | 国内节点覆盖不错 |
| 电信/联通运营商DNS | 各省不同 | 延迟最低,但可能在部分域名上解析结果不理想 |
| 1.2.4.8 | 1.2.4.8 | 国家下一代互联网示范工程,部分环境有奇效 |
我的选择原则是:办公室内网环境优先用内网DNS,因为它能解析内部域名,还能做内网流量的智能调度,公网DNS再好也替代不了这一点。如果内网DNS抽风,临时切换到公网DNS做验证是没问题的,但不要长期混用,否则内部服务名都解析不出来了。
测DNS延迟有个简单办法:dig @服务器地址 example.com,看返回里的Query time。多对比几台,延迟低且解析结果稳定的就是适合作默认配置的。另外,如果你是在云上建了自己的DNS服务器,把“转发器”指向一个稳定可靠的上游也很重要,不然自己搭建的DNS会在递归查询时频频超时。
3. DNS 通过之后:IP 层连通性才是“中间那段看不见的路”
3.1 ping 不通不代表服务一定不可用
确认域名解析没问题后,下一步就是验证目标IP是否可达。很多人习惯性先ping一下,但对ping不通的结果过度解读,反而会带偏方向。
ping使用的是ICMP协议,它测试的是“主机是否在线并且允许响应ICMP请求”。现实情况是:很多生产环境出于安全考虑会禁掉ICMP响应,比如云服务器、负载均衡器的安全组默认就不放行ping,但这完全不影响TCP和HTTP服务正常访问。所以,ping不通的时候,不要急着下“服务器挂了”的结论,先换用TCP层面的端口探测工具验证。
ping真正有价值的地方在于:第一,能快速判断当前IP在网络里有没有路由可达;第二,连续ping一段时间看延迟和丢包率,可以判断链路质量。比如你ping 1.1.1.1延迟只有几毫秒,但ping 某云服务器延迟超过两百毫秒还伴随大量丢包,那基本可以断定中间链路或路由策略有问题,TCP连接自然也不会顺畅。
3.2 traceroute 的用法:找到“黑洞”在哪一段
如果你怀疑中间链路有问题,traceroute(Windows上叫tracert)是下一步的核心工具。它会记录数据包从你本机经过的每一个路由节点,并且打印每一跳的延迟。
我遇到过一个典型场景:客户端访问某个API,有时候通有时候超时,ping目标IP能看到少量丢包。用traceroute一查,发现从本机出去的第三跳开始延迟飙升到300ms,再往后几跳干脆全部* * *超时。这基本说明问题出在中间的运营商链路上,而不是目标服务器本身。
需要注意一条经验:traceroute中间某几跳超时,千万不要直接判定故障。很多运营商路由器为了性能和安全,会限制ICMP的响应,导致显示超时,但数据包仍然正常转发。判断的标准是看最终那一跳能不能到,以及整体延迟是否异常。只有最终跳也超时、或者连续多跳都丢包,才说明路由真的断了。
3.3 用 telnet/nc 验证“端口是否真的开放”
从IP连通再往前一步,就是端口可达性。这一步是最接近TCP排查的入口,也是很多人容易跳过的一步。
telnet 目标IP 端口是个老而实用的工具。如果端口开放,你会看到Connected to或者直接进入一个黑屏光标;如果端口不通,会一直卡住直到超时,然后报Connection refused或者Unable to connect。nc -vz 目标IP 端口在Linux下更适合脚本化检测,还能一次测多个端口。
这里要特别区分两种报错的含义:Connection refused说明IP是通的,目标机器在线,但那个端口上没有服务在监听,或者有防火墙主动回了RST;Connection timed out说明SYN包根本没人应答,大概率是被防火墙安全组静默丢弃了,或者中间链路丢包导致握手包过不去。这两种现象对应的排查方向完全不一样,前者去看服务进程有没有起来,后者去看安全组、iptables规则和网络链路。
3.4 云上环境最容易漏的检查点:安全组
如果是上云的环境,IP层和端口层的“隐形防火墙”一定要优先排除。云服务器的安全组规则、负载均衡器的监听规则、以及VPC内部的网络ACL,任何一层Drop了流量,你在服务器里面抓到包往往什么都看不到,因为包根本没到达网卡。
我处理过几次“服务明明在监听,客户端就是连不上”的案例,最后都是安全组规则只放行了一部分来源IP,或者忘了放行对应端口。碰上这种情况,先别急着在服务器里抓包,去云控制台把安全组、网络ACL、负载均衡监听配置全过一遍,确认源IP、目的端口都放行了再继续看TCP层。
4. TCP 三次握手:连接建不起来时到底卡在哪一步
4.1 握手的本质:三次状态转换
TCP连接建立靠的是三次握手:客户端发送SYN包,服务端收到后回复SYN-ACK包,客户端再回一个ACK包,双方进入ESTABLISHED状态。虽然这是老生常谈,但排障时必须把这三步和本机的连接状态对应起来,才能真正定位问题。
客户端视角:发出SYN后,连接状态是SYN_SENT;收到服务端SYN-ACK,状态会变成ESTABLISHED。如果你在客户端用ss -tn看到大量连接停留在SYN_SENT,说明SYN包发出去了但一直没等到应答。原因可能是对方防火墙拦截、中间链路丢包、或者服务端的接收队列已经满了。
服务端视角:收到SYN后会进入SYN_RECV,表示已经收到连接请求但还没完成握手。如果服务端ss -tn里堆积了大量SYN_RECV,说明对方发来的SYN-ACK应答一直没收到,典型原因是半连接队列溢出,或者客户端回应的ACK丢了。
4.2 用 curl -v 和 ss 判断“卡在握手哪一段”
我最常用的组合拳是curl -v加ss -tn。curl -v会打印连接过程,比如Connected to example.com port 443说明TCP已经建好了,接下来卡住的话就是TLS或HTTP层的问题;如果一直报Connection timed out,说明还在等TCP握手,这时候切到ss -tn看连接状态就非常直观。
举个例子:你访问某个服务超时,ss -tn显示一堆SYN_SENT连接,那就先把目标IP和端口拿出来,在另一个网络环境(比如云服务器本机)试试能不能连。如果本机可以通、你这边不行,多半是本地到目标之间的防火墙或路由问题;如果本机也不行,就是服务端的问题,继续看服务端半连接队列和监听进程。
4.3 半连接队列溢出:一个让连接“时好时坏”的元凶
在TCP握手阶段,服务端维护着两个队列:半连接队列(存放SYN_RECV状态的连接)和全连接队列(存放已完成握手等待应用accept的连接)。这两个队列都有上限,一旦打满,新的连接请求就会被直接丢弃。
这就是为什么有些服务会出现“偶尔连得上、偶尔连不上”的现象:连接量一上来,队列满了,新连接全部卡死,但已有连接还在正常工作,从外部看就是一部不可靠的服务。
排查方法很简单:ss -tn state syn-recv看半连接队列堆积数,ss -lnt里看Send-Q和Recv-Q能反映全连接队列大小和当前积压。如果是Java应用报accept失败或者Nginx报504,配合这两个命令基本能确认队列问题。应急措施是调大net.core.somaxconn、net.ipv4.tcp_max_syn_backlog,以及应用监听接口的backlog参数;治本的办法是看业务流量是否暴涨、后端处理是否过慢,全连接队列堆积通常意味着应用accept速度跟不上连接建立速度。
4.4 防火墙和安全组的“静默丢包”:抓包才能看到真相
很多防火墙配置是“静默丢弃”,也就是直接扔掉你发来的SYN包,不回RST也不回任何东西。客户端这边的表现就是连接一直卡在SYN_SENT直到超时。
要确认这一点,最有效的办法是抓包。Linux下tcpdump -nn -i eth0 tcp port 目标端口可以看到SYN包到底有没有从本机网卡发出去。如果本机已经发出SYN,但一直看不到对端回任何包,那问题几乎肯定在中间某个环节被丢弃了,接下来就该顺着链路往上查防火墙和安全组。
抓包有个容易被忽略的细节:如果在云服务器上用tcpdump抓包,看到SYN进来了但服务端没有回SYN-ACK,这时候先看iptables规则。iptables -L -n -v能看到每条规则的匹配计数,如果某个DROP规则计数在增长,说明是本地防火墙干的。如果计数没增长,再考虑是不是服务进程没有监听这个端口。
4.5 TIME_WAIT 与“端口已在使用”:经典 Java 客户端重连报错
握手建立之后的资源释放阶段,同样有一堆坑,其中最经典的就是java tcp客户端重连时报地址已在使用。这背后是TIME_WAIT状态在作怪。
TCP连接关闭时,主动关闭方会进入TIME_WAIT,默认等待2个MSL(最大分段生存期)后才会释放端口。如果应用在短时间内频繁创建、关闭连接,而每次都用同一个本地端口,就会在重连时报Address already in use。这个问题的根源不在于程序逻辑,而在于TCP协议为了保证旧连接的迟到数据包不会干扰新连接,强制让端口在TIME_WAIT期间不可复用。
我见过好几个用Java或C#写TCP客户端的项目踩这个坑,尤其是配合心跳重连机制时特别容易触发。缓解方式有几个:一是让操作系统允许TIME_WAIT端口尽快复用,net.ipv4.tcp_tw_reuse配合tcp_timestamps启用;二是客户端在重连时不要固定本地端口,让系统随机分配;三是在设计层面控制连接频率,用长连接而不是每次都新建连接。我个人更推荐后两者,因为tcp_tw_reuse虽然能解决端口问题,但它改变的语义需要你对内核参数有充分理解,生产环境不要贸然开启。
5. 连接建立成功之后:握手只是开始,重传和粘包才是真正的暗坑
5.1 DUP ACK、重传与丢包:连接“能用但很慢”的真凶
TCP连接建立成功后,很多人就以为万事大吉了,其实TCP的可靠性机制在这一阶段才开始真正工作。握手只代表双方愿意建立连接,不代表后续数据能稳定送达。最常见的两个问题是重传和重复确认(DUP ACK)。
什么是DUP ACK?简单来说,接收方发现某个数据包丢了或者乱序,就会针对“最后一个按序收到的数据包”重复发送ACK,催促发送方赶紧补发。发送方连续收到几个DUP ACK,就会触发快速重传。如果丢包严重,发送方会走超时重传路线,这时候TCP吞吐量会断崖式下跌。
排查手段有三个层面:第一,ss -tin可以看每个TCP连接的重传次数和发送队列里有没有堆积;第二,ping目标看丢包率和延迟波动,作为链路的粗参考;第三,tcpdump抓包统计重传包和DUP ACK的数量,确定是特定链路段还是全局性问题。我在实际项目中见过不少“接口时快时慢”的案例,最终抓包发现全是重传,链路本身丢包率夸张,TCP层的可靠机制变成了一把双刃剑——它保证了数据无损,但也把延迟放大了几十倍。
5.2 粘包和半包:TCP 是“流”不是“消息”
TCP是字节流协议,它不保证你发送的每一条消息都完整、独立地到达对端。应用层发两次write,底层可能合并成一个包发出去;反过来,一次大消息可能被拆成多个TCP段。这就是粘包和半包问题的根源。
在排查这类问题时,我见过太多程序员怀疑网络设备,结果锅全在应用层。比如C#和Java的TCP客户端互相通信,或者用Modbus TCP协议做设备通讯时,如果双方没有设计明确的消息边界,收方很容易把两条消息读成一条。网上的热搜词里“c# modbus tcp客户端”和“esp01s发送tcp消息手机”都是这个问题的重灾区。
正确的处理方式是应用层约定消息格式,常见有四种:用\n换行分隔、用固定长度、用长度前缀、用特殊终结符。Modbus TCP本身就规定了MBAP头部里有长度字段,但很多单片机实现时不严格按协议组包,也会出现半包问题。我的经验是:先抓包确认底层收到的字节流是什么样,再检查解析逻辑是不是按“完整消息”来读取,而不是按“一次read就是一条消息”来写代码。
5.3 半开连接与 KeepAlive:对方“失踪”了,连接还挂着
TCP还有一种很折磨人的现象:连接看似还活着,实际上对端已经消失了(断电、断网、崩溃),这就是半开连接。TCP协议本身没有内置检测对端是否活跃的机制,除非你主动发包。
客户端和服务端都可能在半开连接上干等。客户端等不到响应,服务端这边看到的就是一堆“假连接”,占用着文件描述符和内存。解决思路有两个层面:应用层主动做心跳,定时发探活消息,超时几次就主动断开重建;内核层面开启TCP KeepAlive,调整net.ipv4.tcp_keepalive_time、tcp_keepalive_intvl、tcp_keepalive_probes三个参数,让内核周期性地发送探测包。
实际项目中,我倾向于应用层心跳为主、内核KeepAlive兜底。原因很简单:内核KeepAlive的探测间隔默认是2小时,对于大部分业务来说太慢了;而且它只能确认“对端IP栈是否活着”,不能确认“对端业务进程是否正常”。真正想做健康检测,还得靠自己设计的心跳协议。
5.4 Nagle 算法与延迟确认:小包业务的延迟杀手
如果你调用的服务是小包高频交互(比如物联网设备的心跳、游戏的实时状态上报),那延迟不稳定多半和Nagle算法与TCP延迟确认的“打架”有关。
Nagle算法会把多个小包合并发送,减少网络上小包数量,但代价是可能增加几十毫秒的延迟。TCP延迟确认机制则是收包后不立刻回ACK,而是等一小段时间,期待能和数据一起捎带回去。这两个机制叠加在一起时,会出现一种经典的“确认死锁”:发送方等着合并更多数据,接收方等着捎带ACK,两边都在等,延迟就肉眼可见地恶化了。
Java和C#里设置TCP_NODELAY(setTcpNoDelay(true))就是关闭Nagle算法,让每个小包立即发送。但这不等于无脑开启,如果你的业务本来就是大块数据传输,Nagle算法反而能提升链路利用率。这个参数是典型的“场景决定论”,不要照搬网上的配置。排查时可以先在抓包里看“两组小包之间的间隔是否异常”,比如明明应该10ms一个包,实际却等到了40ms,那就要怀疑这两个机制在做怪了。
5.5 MTU 与 MSS:为什么小包能通,大包就断
还有一个比较进阶但容易被忽略的坑:MTU(最大传输单元)导致的数据传输失败。症状很怪:TCP握手没问题,小包数据没问题,但只要一传大文件或者大响应体,连接就卡住甚至重置。
原因在于网络链路中某一跳的MTU比你的包小,而你又禁用了路径MTU发现,导致大包被中间设备直接丢弃。排查方法是ping -M do -s 1472 目标IP,测试不同大小的包能不能顺利到达。如果大包不通、小包通,基本就锁定了MTU问题。
解决思路包括调低本机网卡的MTU、调整TCP MSS值(ip link set dev eth0 mtu 1400这种做法),或者确保路径MTU发现正常工作。这个坑在跨运营商、跨国际链路时出现概率特别高,国内访问海外服务时尤其常见。之前处理过一个“从国内服务器访问海外API,小请求正常、大响应超时”的案例,就是典型的MTU问题,把接口响应体压缩到阈值以下立刻恢复正常。
6. 一次完整链路排障复盘:从页面打不开到 TCP 半连接队列溢出
光讲理论容易散,我用一个真实复盘把前面所有步骤串起来。这个案例非常有代表性:某天业务方反馈,官网页面“偶尔打不开”,刷新几次又好了,但过一阵又报超时。用户用的浏览器是Chrome,报的是“无法找到DNS地址”和“连接超时”两种错误交替出现。
当时第一反应当然是先调查DNS。我在用户侧机器上nslookup了一下公司域名,发现默认的内网DNS服务器响应极其不稳定,有时候要等两三秒才返回结果,偶尔直接超时。换个公共DNS一测,解析秒回,结果也正确。到这一步,按常规套路应该把用户机器的DNS改成公共DNS就能解决问题。但奇怪的是,改完后“找不到DNS地址”确实是没了,新问题浮出水面:连接还是间歇性超时。
这说明问题不止一层,DNS只是第一个暴露的故障,后续的TCP层还有问题。于是继续往下查:curl -v访问官网,发现卡在Trying ...之后,紧接着ss -tn看到连接停留在SYN_SENT状态。SYN发出去了,没人应答。这时候就到了“链路连通性”的检查环节。ping目标IP延迟正常、无丢包,telnet 目标IP 443同样超时。那么问题基本锁定在服务端或中间的防火墙。
登录到服务端一看,ss -lnt显示监听端口的Recv-Q积压很大,SYN_RECV状态的连接数量远超正常水位。再查应用进程日志,发现后端业务线程在高峰期处理太慢,全连接队列被占满,内核只能把新进来的SYN包丢弃。结合前面DNS的问题,实际上点有两个:内网DNS因为配置问题导致部分机器解析走了一条慢路径,后续又因为官网流量突增导致TCP半连接队列打满。
最后处理方案是两层同时做:先把内网DNS的转发配置和上游链路重新调整,再把后端服务的accept backlog调大,同时对峰值流量做扩容。这个案例很典型,它说明一个“页面打不开”的现象,背后可能是DNS和TCP两层问题叠加。如果你只处理了DNS,那TCP的坑迟早会让故障换个面目再出现;反之,只调TCP参数也搞不定DNS解析的间歇性失败。这就是为什么“从DNS到TCP一步步定位”这种方式,本质上是在帮你把问题切开,一个接一个解决,而不是靠运气撞上答案。
7. 排障命令速查与沉淀下来的几条经验
7.1 命令速查表:按场景直接找工具
把前面涉及到的常用命令整理成一张速查表,排查时对照着用就行。
| 排查目标 | 命令 | 关键观察点 |
|---|---|---|
| 域名解析是否正常 | nslookup example.com/dig example.com | 返回的A记录、查询耗时 |
| 指定DNS服务器解析 | nslookup example.com 114.114.114.114 | 对比结果是否一致 |
| 清空系统DNS缓存 | Windows:ipconfig /flushdns | 执行后提示“已成功刷新DNS解析缓存” |
| 查看hosts文件 | Windows:C:\Windows\System32\drivers\etc\hosts | 是否有过期的域名映射 |
| IP连通性 | ping -c 5 目标IP | 丢包率、延迟波动 |
| 路由路径 | traceroute 目标IP | 中间跳延迟是否异常 |
| 端口开放检查 | telnet 目标IP 端口/nc -vz 目标IP 端口 | Connection refused还是超时 |
| 查看TCP连接状态 | ss -tn/ss -tn state syn-sent | SYN_SENT堆积、ESTABLISHED数量 |
| 查看监听队列 | ss -lnt | Recv-Q和Send-Q是否积压 |
| 抓包分析 | tcpdump -nn -i eth0 tcp port 443 | SYN、SYN-ACK、重传包 |
| 查看TCP重传 | ss -tin | retrans计数是否持续增长 |
| 防火墙规则计数 | iptables -L -n -v | 被DROP规则的计数是否增长 |
7.2 这些年排障,我总结的几条“心法”
最后分享几条我自己沉淀下来的习惯,不一定写在什么教科书里,但实战中非常有用。
第一,一次只改一个变量。很多故障排查到最后,发现问题是过去两三个小时里别人顺手改过配置导致的。你如果同时改了三处配置,烧香式地重启服务,问题就算消失了你也不知道是哪个改动起的作用。规范操作是先记录当前状态,做一次改动就验证一次,验证通过再动下一个。
第二,抓包要趁早,不要等“实在没办法了再抓”。抓包不是最后手段,而是最有力的证据。tcpdump在问题发生时打开,哪怕只看几十秒,都能帮你把“理论上的问题”变成“证据确凿的问题”。我经常看到有人花一两个小时猜防火墙、猜路由,最后抓包三分钟就定位了。
第三,现象和时间线一定要记录。什么时候开始报错、当时有没有发版、有没有改配置、是不是周期性出现,这些信息比任何命令都值钱。很多神秘故障的答案就藏在“报错时间点”和“变更时间点”的重合里。
网络故障排查这件事,本质上拼的是耐心和条理。技术命令背得再多,没有一套清晰的定位思路,遇到复杂场景还是容易原地打转。按照“DNS → IP连通 → TCP连接 → 连接质量”这条链路一层层验证下去,大多数看起来玄乎的网络问题,最后都会落在某个具体的可修复点上。