
凌晨两点被电话叫醒说某个接口一直超时值班的人第一反应是“网络出问题了吧”然后ping一下是通的就卡在那里不知道下一步了。这种场面我见过太多次。Ping、Telnet、Curl这三个东西几乎是每个做后端、运维、测试的人第一天就会听说的命令但真正能把它们分层用明白的人并不多。绝大多数人止步于“ping 得通就是好的”结果在一堆“看起来通、实际不通”的问题上反复打转。这篇东西想讲清楚一件事这三个工具根本不是同一层的东西它们各自能看到的信息、能证伪的假设、以及会骗你的地方差别非常大。Ping 回答的是“这台机器还活着吗”Telnet 回答的是“这个端口有人守着吗”Curl 回答的是“这个服务在应用层能不能把活干完”。三者是从下往上、逐层剥离的三个观测点。我会把每个命令的关键参数、输出怎么读、什么时候它的结论不可信、以及踩过的坑都摊开讲适合刚入行的同学照着抄作业也适合干了几年但排查全靠经验直觉的老手重新梳理一遍。内容全部是我在真实排查场景里反复验证过的做法命令都是在 Linux 和 macOS 上实测可跑的Windows 下的差异我会单独标注。1. 先把三层观测点分清楚再谈命令怎么敲很多排查效率低根本原因是脑子里没有分层模型抓到一个命令就敲敲完看结果结果符合预期就觉得没问题不符合预期就开始乱猜。Ping、Telnet、Curl 的价值不在于它们各自能不能用而在于它们串起来构成了一条从网络层到应用层的验证链路每一层的结果都能帮你砍掉一半的怀疑范围。1.1 三个命令分别站在哪一层先把定位摆清楚后面的所有讨论都建立在这个基础上。Ping工作在 IP 层之上用的是 ICMP 协议。它发给目标一个 Echo Request类型 8对方回一个 Echo Reply类型 0。整个过程不涉及任何端口不需要目标上跑着什么服务只要目标主机的协议栈还在、路由可达、中间设备没有拦截 ICMP就能收到回应。这意味着 Ping 通只能证明“这台机器在网络上是活的”它对“你关心的那个服务到底活着没有”一无所知。Telnet在这里被当成端口探测器用这是它的一个“副业”。真正的 telnet 协议是明文的远程终端协议现在几乎没人拿它做正经登录了但它的客户端有一个很好用的行为telnet 目标IP 端口会尝试和目标端口建立 TCP 连接。连上了就打印 Connected连不上就报错或者干等。所以它测的是TCP 三次握手能不能完成比 Ping 高了一层能验证“端口上有没有进程在监听”。Curl是应用层的客户端它会把完整的 HTTP/HTTPS 请求发出去拿到响应。它测的是“这个服务能不能正确处理一个真实请求”包含了 TCP 连接、TLS 握手如果是 HTTPS、请求发送、服务端处理、响应返回的全过程。它能看到的东西最多但也最容易把底层问题伪装成上层问题——比如 DNS 解析慢表现出来就是“接口很慢”你不看分段时间根本发现不了。我用一个生活化的类比来串一下Ping 相当于你在楼下喊一嗓子“有人吗”有回声就说明楼里有人Telnet 相当于你走到某扇具体的门前敲门有人应门说明这个房间有人Curl 相当于你把一份文件从门缝塞进去等对方处理完把结果递出来。三个动作验证的东西完全不同缺一环都可能漏判。1.2 什么场景该按什么顺序敲顺序不是随便定的核心原则是从便宜到贵、从粗到细。Ping 最便宜一个包几十字节几乎不消耗什么资源Telnet 要建 TCP 连接稍贵一点Curl 要发完整请求可能触发业务逻辑、写日志、查数据库最贵。所以正常的排查顺序永远是 Ping → Telnet → Curl除非你已经明确知道问题在应用层。但顺序不是教条实际排查里更常见的是根据现象倒推该从哪一层切入。比如现象描述建议起手命令理由完全连不上超时无响应Ping先确认主机是否可达排除路由问题报“Connection refused”Telnet已经有明确错误码说明网络通、端口没监听能连上但接口返回 5xxCurl网络层大概率没问题问题在应用或上游依赖请求很慢但最终成功Curl 加分段计时需要区分是 DNS、建连、还是服务端处理慢偶发失败重试就好Ping 长时间跑 Curl 循环疑似丢包或负载不均这个表我建议直接贴在排查手册里比写一大段文字描述管用得多。它解决的是“遇到问题第一反应敲什么”这个最实际的困惑。注意这张表的前提是你在排查一台你有权探测的目标。对不属于自己管辖的地址做频繁扫描或大包探测在多数环境里是不被允许的实际工作中务必走内部流程确认。1.3 一个必须先建立的认知命令的“成功”不等于服务的“健康”这是我最想强调的一点。Ping 通、端口通、HTTP 200这三件事任何一件成立都不能推导出“服务是健康的”。我遇到过太多次这样的情况curl返回 200但响应体里是一个业务错误码或者 TCP 连接秒建但服务端在拿到请求后卡死几十秒或者 Ping 每秒都通但实际业务流量丢包率高达 5%因为 ICMP 和 TCP 走的队列优先级不一样。所以要建立一个习惯每次敲完命令先问自己“这个结果能证伪什么、不能证伪什么”。Ping 不通不能直接断定机器挂了也可能是 ICMP 被策略拦了Ping 通不能断定端口开着Telnet 连上不能断定应用正常Curl 200不能断定业务逻辑对。这个思维习惯建立起来之后排查效率会有质的变化因为你不再会拿一个层次的结果去推断另一个层次的结论。后面的章节我会分别把这三个命令拆开重点讲“输出里的每一个字段在说什么”和“什么情况下它会骗你”。这两件事恰恰是绝大多数教程不讲的。2. Ping最容易被误读的一个命令Ping 是所有人学会的第一个网络命令也是被误用最多的。它的输出看起来简单实际上每一行都藏着信息尤其是 TTL、mdev 和丢包模式这三处读懂的人不多。2.1 ICMP 报文里到底装了什么先看一个典型输出逐字拆$ ping -c 4 10.0.0.12 PING 10.0.0.12 (10.0.0.12) 56(84) bytes of data. 64 bytes from 10.0.0.12: icmp_seq1 ttl63 time0.412 ms 64 bytes from 10.0.0.12: icmp_seq2 ttl63 time0.398 ms 64 bytes from 10.0.0.12: icmp_seq3 ttl63 time0.421 ms 64 bytes from 10.0.0.12: icmp_seq4 ttl63 time0.405 ms --- 10.0.0.12 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3004ms rtt min/avg/max/mdev 0.398/0.409/0.421/0.009 ms第一行56(84) bytes of data这里有两个数字经常被忽略。56 是ICMP 载荷的字节数也就是你实际发过去的数据长度Linux 默认就是这个值84 是加上 8 字节 ICMP 头和 20 字节 IP 头之后在链路上实际传输的总字节数。这就是为什么下面每行显示的是64 bytes——64 等于 56 的载荷加上 8 字节 ICMP 头。搞清这个换算关系后面做 MTU 探测的时候才知道该填多少。icmp_seq是序号从 1 开始递增。这个字段最大的用途是看连续性如果输出里出现 seq 1、2、4、5中间缺了 3说明第 3 个包丢了或者回程丢了这个信息比最后那个“丢包率 20%”要细得多。丢包是连续丢还是随机丢指向的原因完全不同连续丢往往是链路瞬断或者队列打满随机丢更多是链路误码或者限速策略。ttl63是我认为最被低估的字段。TTL 是 IP 头里的生存时间每经过一个路由设备就减 1减到 0 就丢弃并回一个 ICMP 超时报文。所以 TTL 可以用来粗算跳数不同系统的初始 TTL 不一样Linux 常见 64部分网络设备 255历史上某些系统用 128。上面这个例子里收到 63如果目标是 Linux那跳数大约是 64 - 63 1说明中间只隔了一台设备。这个技巧在判断“请求到底走到了哪一跳”时非常有用尤其是在多机房、多层网关的环境里你能立刻知道流量有没有跑偏。time0.412 ms是往返时延RTT注意是往返不是单向。同一机房内网这个值通常在 1ms 以内同城跨机房 1~5ms跨区域公网 20~50ms跨洲际 150ms 以上。这些数字不是拍脑袋的光在光纤里的传播速度大约是每毫秒 200 公里所以物理距离基本决定了 RTT 的下限任何明显低于这个物理极限的值都值得怀疑。最后一行mdev 0.009 ms是 Linux 特有字段表示时延的平均偏差你可以粗略理解成抖动。mdev 小说明网络稳定mdev 大说明时延忽高忽低。上面这个 0.009ms 非常健康如果看到 avg 是 5ms 但 mdev 是 4ms那说明有些包 1ms 就回来了、有些要 9ms这种抖动对实时性要求高的业务比如音视频、游戏同步是致命的但对普通 HTTP 接口影响有限。2.2 常用参数和跨平台差异Linux 和 Windows 下的 ping 参数命名完全不同这是新手最容易懵的地方。我把最常用的几个对照列出来用途Linux / macOSWindows指定发包数量-c 10-n 10指定载荷大小字节-s 1400-l 1400指定发送间隔秒-i 0.5不支持固定 1 秒单包等待回复超时-W 2秒-w 2000毫秒整个命令的总超时-w 10秒无直接对应不允许分片-M do-f只发不显示-q无指定源地址-I eth0或-I 10.0.0.5-S 10.0.0.5这里面-M do和-f值得单独说。它们的作用是禁止 IP 分片配合不同的载荷大小就能用来探测路径 MTU——也就是从你到目标这条路上单个包最大能过多少字节。做法是从一个较大的值开始试逐步往下降直到不再出现“Packet too big”或者“需要分片但设置了 DF 标志”的错误。具体怎么算以太网的标准 MTU 是 1500 字节这个 1500 包含了 20 字节 IP 头和 20 字节 TCP 头如果是 TCP 流量所以 TCP 的 MSS 最大是 1460。但如果你用 ping 测ICMP 头只有 8 字节那么 1500 - 20 - 8 1472所以ping -M do -s 1472应该刚好能通改成 1473 就会报错。这个 1472 是很多人记不住但很好用的一个常数。# 边界探测1472 通、1473 不通说明路径 MTU 就是 1500 ping -M do -s 1472 -c 2 10.0.0.12 ping -M do -s 1473 -c 2 10.0.0.12如果发现 1472 不通但 1400 通说明链路上有设备把 MTU 调小了常见于各种隧道封装场景。这类问题的典型症状是“小请求正常、大请求卡死”因为 TCP 层虽然会做 MSS 协商但协商只看两端中间设备改小 MTU 后就可能悄悄丢大包表现出来就是页面加载到一半卡住、接口上传大文件失败。这种问题用 telnet 和 curl 都很难定位只有 ping 能直接测出来。2.3 什么情况下 Ping 的结论完全不可信这是关键部分。Ping 的结论在以下几种情况下会严重误导你而这几种情况在生产环境里都不罕见。第一种ICMP 被策略拦截。很多环境出于各种考虑会限制 ICMP 流量这时候 ping 100% 丢包但目标服务完全正常。我吃过这个亏看到 ping 不通就直接往网络故障方向查折腾半天才发现只是探测方式被限制了换成 telnet 端口立马就通。所以判断原则是ping 不通时一定要用端口探测交叉验证不要单凭 ping 的结果下结论。第二种ICMP 被限速。有些设备对 ICMP 做速率限制比如每秒只允许回 10 个。你ping -c 100或者ping -f打过去就会看到大量丢包看起来像网络很差实际上业务流量毫无影响。判断方法是把发送频率降下来ping -i 1 -c 20慢速跑如果慢速不丢、快速丢基本就是限速。第三种ICMP 和 TCP 走不同的队列或路径。在复杂的网络环境里ICMP 可能走低优先级队列或者被策略路由到另一条链路。这时候 ping 的时延和丢包率根本不能代表业务流量的真实情况。我见过 ping 平均 0.3ms 非常漂亮、但 TCP 连接经常超时的场景原因就是两者走的路径不一样。第四种也是最有迷惑性的Ping 通但端口全关。这其实是正常的主机活着不代表服务跑着。但在排查时很多人看到 ping 通就默认“网络没问题”然后把注意力全部转向代码结果绕了一大圈才发现是服务没启动。所以我个人的习惯是除非有明确理由否则永远不要只看 ping 的结果。它只适合做最粗的第一层筛选用来快速排除“目标主机完全失联”这种极端情况。一旦 ping 通过立刻进入下一层别在它身上浪费时间。3. Telnet真正在测的是 TCP 握手不是 telnet先说个容易让人误解的点telnet 10.0.0.12 8080这条命令在排查场景里和 telnet 协议本身一点关系都没有。我们只是借用它的客户端去发起一次 TCP 连接验证目标端口上有没有进程在监听。真正干活的是操作系统的 TCP 协议栈telnet 只是那个“敲门的人”。3.1 三种返回结果对应的三种真相这个命令的输出非常短但三种典型结果的区分度极高这是它最大的价值。结果一连上了。$ telnet 10.0.0.12 8080 Trying 10.0.0.12... Connected to 10.0.0.12. Escape character is ^].看到Connected to就说明 TCP 三次握手完成了。这证明了三件事网络可达、目标主机在、目标端口有进程在监听。注意这里只是握手成功不代表这个端口上的服务能正常处理请求。服务可能刚刚启动还没初始化完可能线程池满了不再 accept可能握手进入 backlog 队列但应用层根本没读——这些情况 telnet 都会显示 Connected但实际业务是坏的。所以别把“端口通”等同于“服务好”。结果二被拒绝。$ telnet 10.0.0.12 9999 Trying 10.0.0.12... telnet: connect to address 10.0.0.12: Connection refusedConnection refused是一个非常明确的信号它意味着目标主机收到了你的 SYN 包但目标端口上没有监听于是协议栈回了一个 RST。这个结果说明网络完全通畅、主机完全正常纯粹是服务没起来或者监听在了别的端口上。看到这个报错排查方向立刻就该转向“进程在不在”“监听地址对不对”不用再纠结网络。顺带说一个特别常见的坑服务监听了127.0.0.1:8080而不是0.0.0.0:8080。这时候你在本机 curl 完全正常但从别的机器 telnet 就是 refused。解决方法是ss -lntp | grep 8080看监听地址如果是127.0.0.1:8080就只能本机访问改成0.0.0.0或者具体网卡地址才行。结果三干等着什么都不返回。$ telnet 10.0.0.12 8080 Trying 10.0.0.12...一直卡在这里几十秒后才超时退出。这种情况最麻烦因为可能性最多可能是防火墙把包 DROP 了不回 RST让你干等可能是目标主机不可达可能是回程路由有问题也可能是中间设备的会话表满了。和Connection refused对比着看就能明白差别refused 是“对方明确告诉你没有”超时是“对方根本不理你”。前者是应用层问题后者是网络或策略问题。这个区分是排查里最有价值的一步能直接决定你接下来往哪个方向走。提示telnet 卡住之后按 CtrlC 中止不了的它捕获了信号。正确退出方式是先按Ctrl]进入telnet提示符再输入quit回车。这个操作很多人第一次遇到会懵。3.2 脚本化场景下 telnet 不好用得换工具telnet 是给人看的不是给脚本用的。它的退出码不可靠输出要靠字符串匹配超时时间也不好控制。如果你要在脚本里做端口探测我推荐下面三种替代方案按可用性排序。方案一ncnetcat$ nc -zv -w 3 10.0.0.12 8080 Connection to 10.0.0.12 8080 port [tcp/*] succeeded!-z表示只扫描不发送数据-v输出详细信息-w 3表示 3 秒超时。退出码 0 表示通、非 0 表示不通脚本里可以直接判断。这是我最常用的方式干净利落。注意不同发行版的 nc 实现GNU netcat、OpenBSD netcat参数略有差异-w在部分版本上含义是“连接后多久无数据就断开”用之前先nc -h确认一下。方案二bash 内置的 /dev/tcp$ timeout 3 bash -c cat /dev/null /dev/tcp/10.0.0.12/8080 echo 端口通 || echo 端口不通这个方案的最大好处是不需要安装任何东西只要机器上有 bash 就能用。原理是 bash 把/dev/tcp/host/port当成一个特殊文件重定向操作会触发真实的 TCP 连接。配合timeout防止卡死。缺点是不支持所有 shelldash 和 sh 里没有这个特性写脚本时 shebang 要明确写#!/bin/bash。方案三curl 的 telnet 模式$ curl -s --connect-timeout 3 telnet://10.0.0.12:8080这个用法比较冷门但好处是只需要 curl 一个工具。--connect-timeout控制建连超时。退出码 0 表示连接成功7 表示连接失败28 表示超时可以细分不同的失败原因。至于 Windows 环境如果是较新的版本PowerShell 4.0 以上用Test-NetConnection最方便Test-NetConnection -ComputerName 10.0.0.12 -Port 8080 -InformationLevel Detailed输出里TcpTestSucceeded直接给 True 或 False比 telnet 清晰得多还顺带把 ping 的结果一并显示了。3.3 端口通了但业务不通Telnet 就到顶了必须清楚 Telnet 的能力边界。它能验证的只有 TCP 握手这一个动作握手成功之后的一切它都看不见。实际排查里经常出现“telnet 秒连、curl 卡死”的情况这恰恰说明问题出在应用层而不是网络层。我遇到过一次很典型的情况某个服务的端口 telnet 通但 curl 请求一直挂起到超时。最后定位是连接数打满了 backlog 队列——内核会先完成三次握手把连接放进 accept 队列但应用层根本没调 accept 去取所以从客户端看握手是成功的可请求永远得不到处理。这种问题用 telnet 看一万遍都是通的只有 curl 加上超时和耗时统计才能暴露出来。所以定位原则是telnet 用来快速区分“网络层问题”还是“应用层问题”。它通了就把网络因素基本排除专心看应用它不通就别在应用层浪费时间先解决网络。这个判断只需要几秒钟性价比极高。4. Curl把整条链路的时间花在哪拆开看如果说 ping 和 telnet 只能给出“通/不通”的二元结论那 curl 提供的是连续的、可量化的观测数据。它能告诉你一个请求从发起 DNS 查询到收到最后一个字节每个阶段各花了多久这是定位“慢”类问题的核心武器。4.1 从 -v 的输出读懂一次请求的完整生命线先用-v看一个请求的详细过程$ curl -v --connect-timeout 5 -m 10 -o /dev/null http://10.0.0.12:8080/health * Trying 10.0.0.12:8080... * Connected to 10.0.0.12 (10.0.0.12) port 8080 GET /health HTTP/1.1 Host: 10.0.0.12:8080 User-Agent: curl/8.4.0 Accept: */* HTTP/1.1 200 OK Content-Type: application/json Content-Length: 27 { [27 bytes data] * Connection #0 to host 10.0.0.12 left intact以*开头的是 curl 自己的信息以开头的是发出去的请求头以开头的是收到的响应头。这个分色规则记清楚一眼就能分辨方向。* Trying表示正在建立 TCP 连接如果这一步卡住问题就在网络或端口和 telnet 的结论一致。* Connected表示握手完成。中间如果走 HTTPS还会有几行 TLS 握手的信息包括证书验证、加密套件协商。 GET之后是请求发出的时刻 HTTP/1.1 200是收到响应头的时刻。这里有几个参数是我几乎每次都会带上--connect-timeout 5只管建连阶段5 秒还没握上手就放弃。这个必须设否则遇到 DROP 的情况会默认等几十秒甚至更久。-m 10是整个请求的总超时包含 DNS、建连、等待响应、下载 body 全部过程。注意它和--connect-timeout是两个独立的上限实际生效的是先到的那个。-o /dev/null把响应体丢掉只关心状态和耗时不往终端刷一堆内容。-s静默模式不显示进度条。但一定要配-S大写否则连错误信息也被吞了排查时会以为命令没跑。还有两个排查专用参数值得记--resolve可以绕过 DNS直接把某个域名指向指定 IP用来区分“DNS 解析错了”和“服务本身有问题”。用法是--resolve host:port:ip比如--resolve api.internal:80:10.0.0.12这样请求还是发到api.internal但实际连的是 10.0.0.12Host 头也不变。另一个是-4/-6强制只用 IPv4 或 IPv6用来排查双栈环境下的协议选择问题——有些环境 IPv6 配了但路由不通客户端优先走 IPv6 就会卡住强制-4立马正常这个现象非常隐蔽。4.2 用 -w 做分阶段计时这才是 curl 的杀手锏-wwrite-out是 curl 最被低估的功能。它可以在请求结束后按你指定的格式输出一堆内部统计变量。这才是定位性能问题的正解。先看完整的可用变量里最关键的几个变量含义累计还是独立time_namelookupDNS 解析完成时刻从请求开始累计time_connectTCP 连接建立完成时刻从请求开始累计time_appconnectTLS/SSL 握手完成时刻从请求开始累计time_pretransfer即将开始传输的时刻从请求开始累计time_starttransfer收到第一个字节的时刻从请求开始累计time_total整个请求结束的总耗时从请求开始累计http_codeHTTP 状态码单一值size_download下载的字节数单一值speed_download平均下载速度字节/秒单一值这里有个极其重要的坑这些时间变量全部是从请求开始算起的累计值不是各阶段独立的耗时。很多人直接把这些数字加起来得出“总耗时 3 秒”的荒谬结论。正确做法是做减法。我常用的一个格式串是这样的curl -o /dev/null -s -w DNS解析: %{time_namelookup}s TCP建连: %{time_connect}s TLS握手完成: %{time_appconnect}s 首字节到达: %{time_starttransfer}s 请求总耗时: %{time_total}s HTTP状态码: %{http_code} 下载字节数: %{size_download} http://10.0.0.12:8080/health要得到各阶段的独立耗时做减法DNS 耗时time_namelookupTCP 建连耗时time_connect - time_namelookupTLS 握手耗时time_appconnect - time_connect纯 HTTP 请求这项为 0请求发送 服务端处理 首字节返回time_starttransfer - time_appconnectHTTP 请求则是time_starttransfer - time_connect内容传输耗时time_total - time_starttransfer举个实际算例。假设某次请求的输出是DNS解析: 0.012000s TCP建连: 0.057000s TLS握手完成: 0.192000s 首字节到达: 0.835000s 请求总耗时: 0.841000s那么拆出来是DNS 12msTCP 建连 57 - 12 45msTLS 握手 192 - 57 135ms服务端处理加首字节 835 - 192 643ms内容传输 841 - 835 6ms。这份拆解的价值立刻就体现出来了大头在服务端处理占了 643ms占比 76%。DNS、建连、TLS 加起来才 192ms。这时候你的排查方向非常明确——去看服务端的处理逻辑、看数据库慢查询、看下游依赖调用而不是纠结网络。反过来如果输出是这样的DNS解析: 0.480000s TCP建连: 0.495000s TLS握手完成: 0.510000s 首字节到达: 0.528000s 请求总耗时: 0.530000s拆解一下DNS 480ms占了 90%建连只有 15msTLS 15ms服务端处理 18ms。这时候问题一目了然——DNS 解析慢可能是 DNS 服务器响应慢、可能是配置了多个 DNS 服务器而第一个超时、也可能是用了本地缓存没命中。跟服务端代码毫无关系改代码改到天亮也没用。我给这些阶段定了几个参考阈值用来快速判断哪些数字不正常内网环境的标准公网要放宽阶段健康范围内网异常信号DNS 解析 5ms有缓存或 50ms超过 100ms 说明 DNS 有问题TCP 建连 1ms同机或 5ms同机房超过 20ms 检查网络路径TLS 握手 30ms内网超过 100ms 检查证书链和会话复用首字节 100ms超过 500ms 说明服务端有瓶颈内容传输取决于体积看 speed_download速度明显低于带宽预期这些阈值不是绝对的要靠平时在正常环境下多跑几次建立基线。有了基线异常就无处遁形这比任何经验直觉都靠谱。4.3 几个高频实战用法除了计时curl 还有几个用法在排查里特别顺手。只看响应头不下载内容。curl -I http://10.0.0.12:8080/health发的是 HEAD 请求只拿头信息。速度快适合批量检查服务健康状态。但要注意有些服务端不实现 HEAD会返回 405这时候就得用curl -s -o /dev/null -D -效果一样但用的是 GET。跟随重定向看最终落到哪。curl -sIL http://10.0.0.12:8080/api会把每一跳的重定向都打印出来最后给出最终状态码。排查“接口莫名返回 301”这类问题时非常有用能清楚看到跳转链路。只看状态码适合写循环。curl -s -o /dev/null -w %{http_code} http://10.0.0.12:8080/health只输出三个数字脚本里直接判断。配合for循环可以做一个简易的持续探测for i in $(seq 1 20); do code$(curl -s -o /dev/null -m 3 -w %{http_code} http://10.0.0.12:8080/health) echo $(date %H:%M:%S) - $code sleep 3 done这段跑一分钟就能看出是持续失败还是间歇性失败。间歇性失败和持续失败的原因完全不同前者更可能是负载不均、连接池耗尽或者某个实例异常。POST 请求和超时验证。排查写接口时curl -X POST -H Content-Type: application/json -d {key:value} -m 5 http://10.0.0.12:8080/api是标配。我建议所有排查用的 POST 请求都带上-m避免在某些异常情况下无限挂起把排查窗口拖得很长。注意-m的值不要设得太短。我见过有人图快设成-m 1结果把一个本来要 1.2 秒才能返回的正常慢接口误判成故障。设置超时前先确认这个接口的合理响应时间上限是多少。5. 常见问题速查与踩坑实录前面讲的都是“正常情况下怎么看”这一节讲的是“不正常的时候怎么想”。我把这些年遇到过的典型现象整理成了一张表配合几个印象最深的踩坑经历希望能帮你少走点弯路。5.1 现象速查表现象最可能的原因下一步动作Ping 不通Telnet 通ICMP 被策略限制忽略 ping 结果用端口探测为准Ping 通Telnet 超时端口未监听 / 防火墙 DROP在目标机执行ss -lntp确认监听状态Telnet 报 Connection refused端口无监听主机正常检查服务进程和监听地址Telnet 通Curl 卡住不返回应用层无响应 / backlog 打满用 curl 分段计时看卡在哪个阶段Curl 返回 000连接失败或超时未收到 HTTP 响应加-v重跑看具体失败在哪一步Curl 返回 502/504网关到后端的链路有问题查网关日志和后端实例健康状态请求快但下载慢带宽限制或内容体积大看speed_download和size_download小请求正常大请求失败路径 MTU 不匹配用ping -M do逐步探测 MTU 边界偶发超时重试就好丢包 / 某实例异常 / 连接池不足循环探测定位规律检查负载均衡配置首次请求慢后续快DNS 缓存未命中 / 连接未复用对比多次请求的time_namelookup这张表我建议保存在手机备忘录里半夜被叫醒的时候脑子是不清醒的有个对照表能省掉大量试错时间。5.2 几个印象深刻的踩坑经历坑一把 ping 的结果当成了最终结论。有一次排查线上接口超时第一步 ping 目标机丢包率 30%我立刻断定是网络抖动然后花了两个小时去查链路监控、找网络团队协调最后发现 ICMP 在那个网段被限速了实际业务流量一切正常真正的问题是某个下游依赖的连接池打满了。这次之后我给自己定了条规矩ping 的结果永远只当参考不当证据。至少要用 telnet 或 curl 交叉验证一次才能形成判断。坑二Connection refused 被误判成网络不通。新人经常把Connection refused和网络故障混为一谈实际上这两个结论南辕北辙。refused 恰恰说明网络好得很包能过去也能回来不然你怎么收到 RST问题 100% 在服务端。我现在带人的时候第一课就是让他们记住这个区别超时是网络问题refused 是进程问题。这一句话能省掉很多无效排查。坑三curl 的累计时间被当成独立耗时。我一开始用-w的时候看到几个时间加起来跟总耗时对不上还以为工具出问题了。后来才搞明白所有时间变量都是从请求起点的累计值。这个坑其实很隐蔽因为如果你只关心“哪个阶段最慢”看单个变量的绝对值也能看出一点端倪很容易蒙混过关但只要涉及到精确的阶段拆分不做减法就会得出完全错误的结论。我在团队内部分享的时候专门强调过这一点因为算错方向可能导致你去优化一个根本不是瓶颈的环节。坑四监听地址是 127.0.0.1本机怎么测都通。这个坑阴险在于它“看起来一切正常”。服务在本机 curl 完全没问题日志也干干净净但你从任何其他机器都连不上。我第一次遇到的时候排查了很久最后ss -lntp一看就笑了——监听在127.0.0.1:8080只有本机回环能访问。现在这成了我排查连通性问题的标准第二步第一步 telnet 确认端口状态第二步就是上目标机看监听地址。顺带说IPv6 环境下还有个类似的问题服务监听了::1而不是::同样只有本机能访问。坑五加了 -m 但没加 --connect-timeout仍然会长时间挂起。这两个参数我之前也以为是包含关系实测下来不是。-m管总时长没错但在某些 TCP 建连卡在 SYN 重传的场景里curl 对总超时的响应并不总是及时。加上--connect-timeout会强制在建连阶段就切断行为更确定。现在我写任何探测脚本都是两个一起加宁可多打几个字。5.3 一个我常用的组合脚本最后分享一个我日常排查时最常用的组合思路就是三层串起来跑一遍把结果一次性收集齐#!/bin/bash HOST10.0.0.12 PORT8080 URLhttp://${HOST}:${PORT}/health echo 第 1 层ICMP 探测 ping -c 4 -W 2 ${HOST} 21 | tail -3 echo echo 第 2 层TCP 端口探测 if timeout 3 bash -c cat /dev/null /dev/tcp/${HOST}/${PORT} 2/dev/null; then echo 端口 ${PORT} 可连接 else echo 端口 ${PORT} 不可连接超时或被拒绝 fi echo echo 第 3 层应用层探测 curl -s -o /dev/null --connect-timeout 3 -m 10 -w DNS解析: %{time_namelookup}s TCP建连: %{time_connect}s 首字节到达: %{time_starttransfer}s 总耗时: %{time_total}s 状态码: %{http_code} ${URL}这个脚本的价值在于一次执行就能拿到三层的信息不用来回切命令。如果第 1 层就挂直接找网络第 2 层挂找服务进程第 3 层的时间拆分异常就往应用方向查。我把它做成了一个 shell 函数放在自己的.bashrc里参数化 host 和 port半夜排查的时候一行命令就能出结论比现敲快得多。有个小细节值得说脚本里用tail -3只保留 ping 的统计行。因为排查时你真正关心的往往是有没有丢包、平均时延多少前面那一堆64 bytes from反而干扰视线。如果发现有丢包再单独跑一次完整输出看丢包的分布规律。还有一点第 2 层我用的是 bash 的/dev/tcp而不是 nc理由是兼容性最好绝大多数 Linux 发行版默认都有 bash不需要额外装包。如果你确定环境里有 nc换成nc -zv -w 3也行输出更友好一些。这套东西我用了好几年从单机服务到容器集群都能套用唯一需要调整的就是端口和路径。真正把这三个命令的边界想清楚之后你会发现排查这件事其实是有确定路径的大部分时间浪费在“不知道该看哪一层”上而不是“看了但看不懂”。先把分层的直觉建立起来剩下的都是熟练度问题。