Skip to content

14|网络诊断与排错

服务起了但外部访问不了。ping 一下——通,说明本机到目标网络层是通的。curl 一下——报 connection refused,说明目标端口没开;报 connection timed out,说明被防火墙挡了或网络不通。同样是"访问不了",三种现象指向三种不同根因。

网络故障排查的核心方法是分层定位、由近及远:从本机最近的一层开始,逐层向外确认。底层断了上层一定不通,跳过底层直接查上层一定走偏。本篇讲分层排查的标准顺序,每一层用什么工具、看什么信号,再展开常见网络故障的判定和处理。

一、网络排查的标准分层

层次确认什么主要工具
1. 物理层网卡 link 是否 up,速率是否正常ip linkethtool eth0
2. 网络层(本机)网卡有没有 IP,IP 是否正确,路由表是否合理ip addrip routeip route get <目标>
3. DNS域名能否解析,解析结果是否正确getent hostsdig
4. 网络层(连通性)能否到达目标 IP,中间路径在哪一跳断pingmtrtraceroute
5. 传输层目标端口是否开放,TCP 是否能建立ssnccurl -v
6. 应用层应用是否正常响应,响应内容是否符合预期curl、应用日志
7. 数据包级包到底走到哪一步、被谁丢了tcpdumptshark

遵循这个顺序的好处:每一层都用最便宜的工具(查命令几秒钟),问题往往在前几层就暴露,根本不需要走到抓包这一步。直接上抓包是最低效的排查方式。

第 0 步永远是这四条:摸基本面

不管什么网络问题,先把这四条跑一遍,大量问题这一步就暴露了:

bash
ip addr                 # 网卡是否 UP、IP 是否符合预期
ip route                # 默认路由和静态路由
cat /etc/resolv.conf    # DNS 服务器
ss -lntp                # 本机监听的所有端口

跑完这四条后,十有八九能定位到问题方向——网卡 DOWN、缺默认路由、DNS 配错、服务没起来,都是肉眼可见的。

二、ping 与 ICMP 局限

bash
ping -c 4 8.8.8.8           # ping IP
ping -c 4 example.com       # ping 域名
ping -I eth1 -c 4 8.8.8.8   # 指定从 eth1 发出,验证特定网卡的连通性

判定逻辑:

  • IP 能通、域名不通 → DNS 问题
  • IP 也不通 → 继续查路由、防火墙、对端可达性

ping 不通不等于业务不通

很多服务器和网络设备主动禁用 ICMP 响应(为了防探测、防 DDoS),所以 ping 不通不能用来证明业务端口不通。判断业务可达,直接测业务端口:

bash
nc -zv host 80              # 测 TCP 80 端口
curl -v http://host:80       # 更彻底的 HTTP 测试

反过来,ping 通也不等于业务通——能 ping 通只说明 ICMP 路径可达,业务端口可能被防火墙单独拦掉。两个工具配合用,结论才靠谱。

MTU 探测

ping 一个少被用的能力——-M do -s <size> 探测路径 MTU。这在排查 MTU 黑洞时是关键工具:

bash
# 发 1472 字节的 ICMP(加上 28 字节 IP/ICMP 头 = 1500,标准以太网 MTU),不分片
ping -M do -s 1472 -c 3 8.8.8.8
# 输出 OK 表示路径 MTU >= 1500

# 发 1473 字节,不分片
ping -M do -s 1473 -c 3 8.8.8.8
# 输出 "Frag needed and DF set" 表示路径 MTU < 1501

# 二分法找到准确 MTU
ping -M do -s 1400 -c 3 8.8.8.8     # OK?
ping -M do -s 1450 -c 3 8.8.8.8     # OK?

MTU 相关故障在第八节"MTU 黑洞"展开。

三、traceroute 与 mtr:看路径

traceroute 显示数据包经过的路径,逐跳定位:

bash
traceroute -n 8.8.8.8       # -n 不解析主机名,加快显示
traceroute -T -p 443 host    # 用 TCP SYN 探测(很多设备 ICMP 被禁但 TCP 能过)

mtrtraceroute + ping 的合体版本,持续统计每一跳的丢包率和延迟,排查链路质量问题时比 traceroute 强得多:

bash
mtr 8.8.8.8                  # 默认交互界面
mtr -rwbc 30 8.8.8.8         # -r 报告模式, -w 宽输出, -b 同时显示 IP, -c 30 发 30 个包

-rwbc 30 的输出格式适合写报告、贴工单:

Host                                Loss%   Snt   Last   Avg  Best  Wrst StDev
1. 192.168.1.1                       0.0%    30    0.5   0.5   0.3   0.8   0.1
2. 10.0.0.1                          0.0%    30    1.2   1.3   1.1   2.0   0.2
3. ???                              100.0%    30    0.0   0.0   0.0   0.0   0.0
4. 203.0.113.5                       0.0%    30    8.0   8.5   7.8  12.0   0.9
5. 8.8.8.8                           0.0%    30    9.0   9.2   8.8  11.0   0.5

中间跳 100% 丢包不一定是真故障

最大的误判点:看到中间某一跳丢包率高甚至 100%,不能直接断定那一跳的设备有问题。原因:

  • 很多路由器限制 ICMP 回复速率(rate limiting)——一秒钟只回应几个 ICMP,超过的丢弃
  • 部分设备完全不回复中间跳的 TTL 过期消息(传统上叫 "ICMP time exceeded"),但仍然正确转发数据包
  • 上面输出里第 3 跳显示 ??? 100% 丢包,但第 4、5 跳正常 → 第 3 跳只是不回应,转发功能正常

真正要看的是最终目的地的丢包率和延迟。终点正常 = 链路正常,中间跳的丢包是工具显示限制造成的假象。

终点真有丢包时,从尾跳往前看,第一个开始出现高丢包率的跳点 = 实际故障点——而不是最后一跳。

四、curl:HTTP 层验证

HTTP 层排查首选 curl,功能比 wget / telnet / 浏览器都丰富:

bash
curl -I http://example.com               # 只看响应头(HEAD 请求)
curl -v http://example.com               # 显示完整请求-响应过程
curl -fsS http://127.0.0.1:8080/healthz  # 健康检查标准写法
curl -w "@curl-format.txt" -o /dev/null -s http://example.com  # 看详细计时

高频参数速查

参数作用适用场景
-I只发 HEAD,只看响应头快速看状态码、Content-Type
-v显示连接全过程(DNS、TLS、请求头)排查 SSL 握手、重定向问题
-f4xx/5xx 时退出码为 22(失败)健康检查、自动化脚本
-sS静默但保留错误输出自动化脚本中抑制进度条但保留 stderr
-L自动跟随重定向处理 301/302 跳转
-k忽略 TLS 证书校验内网测试,生产脚本禁用
--resolve host:port:ip强制把域名解析到指定 IP测试单台后端是否正常,绕过负载均衡
--connect-timeout N连接阶段超时(秒)防止脚本卡死
-m N整体超时(秒)防止单个请求卡死

--resolve 在多副本排查中的价值

线上服务通常通过域名指向负载均衡器,后面挂多个真实后端。某次故障"50% 的请求失败"——很可能是某一个后端节点出问题,但通过域名访问每次随机命中不同后端,无法稳定复现。

--resolve 让你不修改 DNS 就能精准测试某一台后端:

bash
# api.example.com 实际是 LB,后面有 3 台后端
curl --resolve api.example.com:443:10.0.0.1 https://api.example.com/healthz
curl --resolve api.example.com:443:10.0.0.2 https://api.example.com/healthz
curl --resolve api.example.com:443:10.0.0.3 https://api.example.com/healthz
# 找到失败的那一台

curl 的详细计时:定位"慢"在哪一阶段

curl -w 配合一个格式文件,可以看请求每个阶段的耗时:

bash
cat > /tmp/curl-format.txt <<'EOF'
   namelookup:  %{time_namelookup}s
      connect:  %{time_connect}s
   appconnect:  %{time_appconnect}s   (TLS 握手)
  pretransfer:  %{time_pretransfer}s
     redirect:  %{time_redirect}s
starttransfer:  %{time_starttransfer}s  (首字节返回)
        total:  %{time_total}s
EOF

curl -w "@/tmp/curl-format.txt" -o /dev/null -s https://example.com

输出示例:

   namelookup:  0.0234s
      connect:  0.0892s
   appconnect:  0.3421s   (TLS 握手)
  pretransfer:  0.3422s
starttransfer:  0.4567s   (首字节返回)
        total:  0.4892s

排查"接口慢"时,这几个时间段的拆分能立刻定位:

  • namelookup 高 = DNS 解析慢
  • connect - namelookup 高 = TCP 握手慢(网络层)
  • appconnect - connect 高 = TLS 握手慢(证书链/CPU)
  • starttransfer - appconnect 高 = 服务端处理慢(应用层)
  • total - starttransfer 高 = 响应体下载慢(带宽/响应体过大)

五、DNS 诊断

DNS 问题占网络问题的相当比例,且常常被误判为"网络不通"。

bash
getent hosts example.com         # 走系统 NSS,模拟应用真实的解析路径
dig example.com                  # 直接查 DNS,绕过 hosts 和缓存
dig @8.8.8.8 example.com         # 指定 DNS 服务器,对比不同 DNS 的返回
dig +trace example.com           # 从根域名服务器开始逐级追踪
dig -x 8.8.8.8                   # 反向解析,IP → 域名

getent 与 dig 必须对比看

应用程序解析域名走的是系统 NSS(nsswitch.conf 配置的顺序,通常是 files dns),dig 直接查 DNS 服务器,绕过本地配置。两者结果不一致是 DNS 故障最经典的特征:

  • dig example.com → 新 IP
  • getent hosts example.com → 旧 IP

这种情况:

bash
# 看是否被 /etc/hosts 截胡
grep example.com /etc/hosts

# 看 NSS 配置顺序
grep '^hosts:' /etc/nsswitch.conf
# hosts: files dns    ← files 在 dns 前,/etc/hosts 优先

/etc/hosts 里写死的旧记录、负载均衡切换后忘了清的临时记录,都会导致应用一直连到错误的目标。

dig +trace:层级解析失败定位

dig +trace 模拟递归解析过程,从根域名服务器(.)开始,依次查询 .comexample.comwww.example.com,显示每一级的返回:

bash
dig +trace example.com

哪一级返回 SERVFAIL 或者返回了非预期的 NS,就说明那一级的权威服务器有问题。这是排查"自家域名解析失败"的核心手段。

DNS 解析慢的判定

应用偶尔出现"刚启动后第一个请求超时几秒,后面正常"——大概率是 DNS 解析慢:

bash
time dig @<本机 DNS> example.com
# 如果 real 时间超过 100ms,DNS 性能就有问题

可能原因和处理:

  • DNS 服务器响应慢 → 换 DNS 服务器,或在本机部署 dnsmasq / unbound 做本地缓存
  • IPv6 解析顺序问题 → /etc/gai.conf 调整 IPv4/IPv6 优先级
  • /etc/resolv.confoptions 参数不合理 → 调短 timeout、调小 attempts(详见第 13 讲第 5 节)

六、ss:查看本机连接

ss 是查看本机网络连接的主力工具,取代了老的 netstat(net-tools 包提供,已不推荐)。

常用命令

bash
ss -lntp               # 查看 TCP 监听端口及进程(-l 监听 -n 不解析 -t TCP -p 进程)
ss -lnup               # UDP 监听
ss -antp               # 所有 TCP 连接(包括非监听状态)
ss -antp | grep ':3306' # 谁连了 MySQL
ss -tn state established 'dst :443'   # 所有出站 HTTPS 连接
ss -s                  # 连接状态统计概览

ss -s 是排查连接数问题的快速概览:

$ ss -s
Total: 234
TCP:   189 (estab 156, closed 22, orphaned 0, timewait 22)

Transport Total     IP        IPv6
RAW       0         0         0
UDP       8         5         3
TCP       167       133       34
INET      175       138       37
FRAG      0         0         0

TCP 连接状态速读

State        含义                                                      问题信号
LISTEN       服务在监听等连接                                         正常
ESTAB        连接已建立,正常传数据                                    正常
SYN-SENT     客户端发了 SYN,等服务端回应                              大量 = 目标端口不响应或被防火墙
SYN-RECV     服务端收到 SYN,发了 SYN-ACK,等客户端 ACK                 大量 = 可能是 SYN flood
FIN-WAIT-1   主动关闭方发了 FIN,等对方 ACK                            大量 = 对端处理慢或半开
FIN-WAIT-2   收到了对方的 ACK,等对方的 FIN                            大量 = 对端不发 FIN(应用 bug)
CLOSE-WAIT   被动关闭方,等本地应用 close()                            大量 = 本地应用漏 close
TIME-WAIT    主动关闭方,等待 2*MSL                                    高并发短连接下大量是正常的
LAST-ACK     被动关闭方,发了 FIN,等对方 ACK                           短暂出现

按状态过滤:

bash
ss -ant state established | wc -l        # ESTAB 数量
ss -ant state time-wait | wc -l           # TIME-WAIT 数量
ss -ant state close-wait                  # 看哪些连接卡在 CLOSE-WAIT(应用漏 close 的典型信号)

七、Connection refused vs Connection timed out

这是 TCP 连接失败时最关键的两种错误,反映完全不同的问题,处理方向完全不同

错误含义典型耗时排查方向
Connection refused包到达对方,但端口拒绝(没监听或防火墙 reject)立即返回(< 1 秒)服务端:ss -lntp 看是否监听、防火墙是否 reject
Connection timed out包根本到不了对方,或对方收到但回包到不了几十秒(SYN 重试)网络层:路由、防火墙 drop、对方主机离线

判定根据:错误返回的速度

  • refused 是立即的——SYN 发出去,对方回了 RST(reset),本机立刻把 RST 翻译成 Connection refused
  • timed out 是慢的——SYN 发出去,没有任何回应,本机 TCP 协议栈按指数退避重试 3-5 次,总耗时几十秒,最后超时

如果错误是立即返回的,直接去服务端查——这意味着包能到对方机器:

bash
# 在服务端
ss -lntp | grep ':<端口>'      # 服务是否监听?监听在 0.0.0.0 还是 127.0.0.1?
iptables -L INPUT -n | grep <>   # 防火墙是否 reject?

如果错误是慢的(几十秒后超时),先不要去对方机器——大概率包根本没到对方。先在本机查链路:

bash
ping <对方IP>                  # ICMP 是否能通?
mtr -rw <对方IP>                # 路径在哪一跳断?
ip route get <对方IP>           # 流量走哪个网卡?
iptables -L OUTPUT -n           # 出方向防火墙是否拦?

八、企业里常见的网络故障场景

下面是网络层与传输层频繁遇到的具体故障,每条给现象、判定特征、处理路径。

conntrack 表满

现象:服务器突然大量新连接建立失败,但已有连接正常。dmesg 出现:

nf_conntrack: table full, dropping packet

根因:Linux 启用 nftables/iptables 的 conntrack 模块时,每个连接占用一个 conntrack 条目。条目数有上限(net.netfilter.nf_conntrack_max),满了后新连接被丢弃。常见于:

  • 高并发短连接业务(API 网关、Nginx 反代)
  • 客户端不规范导致大量 TIME_WAIT
  • 被慢速 SYN 扫描

判定与处理:

bash
# 查看当前 conntrack 使用率
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 如果 count 接近 max,基本可以确认

# 临时调大(立即生效)
sysctl -w net.netfilter.nf_conntrack_max=1048576

# 持久化
echo "net.netfilter.nf_conntrack_max = 1048576" >> /etc/sysctl.d/99-conntrack.conf
sysctl -p /etc/sysctl.d/99-conntrack.conf

# 调小 TIME_WAIT 在 conntrack 中的保留时间
echo "net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30" >> /etc/sysctl.d/99-conntrack.conf

预防:监控指标采集 node_nf_conntrack_entries / node_nf_conntrack_entries_limit 使用率,超过 80% 告警。云原生场景(K8s 节点)默认 conntrack 上限通常不足,大流量集群必须调大。

TIME_WAIT 大量堆积

现象:ss -ant state time-wait | wc -l 显示几万甚至几十万 TIME_WAIT 连接。同时业务报"无法分配新端口"(Cannot assign requested address)。

根因:TIME_WAIT 是主动关闭连接方的正常状态,持续 60 秒(2 倍 MSL)。高并发短连接场景下(每秒几千次新连接),TIME_WAIT 会堆积。如果是作为客户端主动连接外部服务,会占用本地临时端口范围(默认 32768-60999,约 28K 个),端口耗尽后无法发起新连接。

判定:

bash
# TIME_WAIT 数量
ss -ant state time-wait | wc -l

# 临时端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 默认 32768  60999,约 28K 个可用端口

# 看具体连接方向(本机作为客户端连了多少远端)
ss -ant state time-wait | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -rn | head

处理:

bash
# 1. 扩大本地临时端口范围
echo "net.ipv4.ip_local_port_range = 1024 65535" >> /etc/sysctl.d/99-tcp.conf

# 2. 允许 TIME_WAIT 状态的端口被复用(仅对出站连接安全)
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.d/99-tcp.conf

sysctl -p /etc/sysctl.d/99-tcp.conf

注意:net.ipv4.tcp_tw_recycle 在 Linux 4.12 已被移除,不要使用——它在 NAT 环境下会导致丢包。tcp_tw_reuse 是安全的替代,且只对出站连接生效。

根本解决:应用改用连接池或长连接,减少短连接的频次。短连接到 Redis、MySQL、上游 HTTP 服务这类场景,连接池(jdbc pool、redis-py 的 connection pool、Nginx 的 keepalive)是必要的。

CLOSE_WAIT 堆积

现象:ss -ant state close-wait 显示某个进程上有大量 CLOSE_WAIT 连接,且数量持续增长不释放。

根因:CLOSE_WAIT 是被动关闭方的状态——对端发了 FIN 表示要关连接,本机内核回了 ACK,但本地应用没有调用 close() 真正关闭 socket。这是应用程序 bug,通常是错误处理路径漏了 close。

判定:

bash
# 看哪个进程持有大量 CLOSE_WAIT
ss -antp state close-wait | awk '{print $NF}' | sort | uniq -c | sort -rn

# 该进程打开的所有 socket
lsof -p <PID> | grep -i tcp

处理:这是应用 bug,只能改代码——加上正确的 close 逻辑、用 try-with-resources(Java)/ context manager(Python) / defer close()(Go) 包住 socket 操作。运维侧只能临时通过重启进程缓解,根治需要开发修代码。

CLOSE_WAIT 堆积长期不处理,最终会耗尽进程的文件描述符上限,服务彻底无法接受新连接。

SYN 队列(半连接队列)溢出

现象:客户端报 Connection timed out,服务端 ss -lnt 看到目标端口的 Recv-Q 接近或等于 Send-Q,dmesg 可见:

TCP: request_sock_TCP: Possible SYN flooding on port 80. Sending cookies.

根因:三次握手时,服务端收到 SYN 后,把连接信息放入"半连接队列"(SYN_RECV 状态),等待客户端 ACK。这个队列有大小限制(net.ipv4.tcp_max_syn_backlog),满了后新 SYN 被丢弃,表现为客户端连接超时。

可能是真的 SYN flood 攻击,也可能是正常业务流量过大触发。

判定:

bash
# 看半连接数量
ss -ant state syn-recv | wc -l

# 当前限制
cat /proc/sys/net/ipv4/tcp_max_syn_backlog

# 看监听 socket 的队列
ss -lnt | grep ':80'
# State    Recv-Q   Send-Q  Local Address  Peer
# LISTEN   128      128     0.0.0.0:80     ...
# Recv-Q 表示当前 SYN_RECV 数量,Send-Q 表示队列上限

处理:

bash
# 调大半连接队列上限
echo "net.ipv4.tcp_max_syn_backlog = 65535" >> /etc/sysctl.d/99-tcp.conf

# 启用 SYN cookies(防 SYN flood 的标准手段,默认已开)
echo "net.ipv4.tcp_syncookies = 1" >> /etc/sysctl.d/99-tcp.conf

# 应用层的 backlog 也要调大(Nginx 的 listen 指令)
# server {
#     listen 80 backlog=65535;
# }

sysctl -p /etc/sysctl.d/99-tcp.conf

应用层 listen() 系统调用传的 backlog 参数,与 tcp_max_syn_backlog 共同决定实际队列大小——内核取两者最小值。所以两边都要调。

MTU 黑洞(PMTU Black Hole)

现象:大请求/大响应失败,小请求成功。例如:

  • curl http://host/healthz OK(响应几十字节)
  • curl http://host/api/large-data 卡死(响应几 MB)
  • SSH 登录 OK,执行 ls -la 输出小没问题,执行 cat largefile 卡死

根因:MTU 黑洞是 PMTU(Path MTU)发现失败导致的故障。正常 TCP 在握手时会协商 MSS(Maximum Segment Size),如果路径中间某段 MTU 小于协商值,中间设备应该发回 ICMP "Fragmentation Needed",本机收到后调小 MSS 重传。

但很多防火墙错误地把所有 ICMP 一刀切丢弃(包括 PMTU 发现需要的"Fragmentation Needed"消息),导致:

  • 小包能过(小于 MTU)
  • 大包被中间设备丢弃,且本机收不到通知,只能反复重传至超时

判定:用 ping -M do -s 手工探测路径 MTU:

bash
# 标准以太网 MTU 1500,减去 IP 头(20)和 ICMP 头(8)= 1472 payload
ping -M do -s 1472 -c 3 <目标IP>
# 成功说明路径 MTU >= 1500

ping -M do -s 1400 -c 3 <目标IP>
# 失败说明路径 MTU < 1428(包括头)
# 二分定位准确 MTU

典型场景:

  • VPN/隧道(IPSec、GRE、WireGuard、VXLAN)会增加额外封装头,MTU 比标准以太网小(常见 1400-1480)
  • 跨公网链路某段配了 jumbo frame 而你这边没有
  • 容器网络(Flannel VXLAN 模式 MTU 默认 1450)

处理:

bash
# 方式 1:调小本机网卡 MTU 适配最小 MTU
ip link set eth0 mtu 1400

# 方式 2:用 iptables 强制改写 TCP MSS(更精细)
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN \
    -j TCPMSS --clamp-mss-to-pmtu

# 方式 3:让中间设备放行 ICMP "Fragmentation Needed"

容器/K8s 环境的 MTU 问题:节点物理网卡 MTU 1500,VXLAN 封装后 Pod 网卡应该用 1450。如果 Pod 网络模型设置错(用了 1500),容器内 Pod 互访大包会卡死。

丢包定位的分层方法

业务报"偶发延迟高、间歇丢包",定位哪一层在丢:

网卡层丢包:ip -s linkethtool -Serrors / dropped

bash
ip -s link show eth0
# RX: errors > 0 = 物理层错误
# RX: dropped > 0 = 内核来不及处理,ring buffer 满

协议栈丢包:/proc/net/snmp 看各种计数器

bash
cat /proc/net/snmp
# InReceives 总收包
# InDiscards 内核丢弃
# RetransSegs TCP 重传次数,持续增长 = 链路丢包

netstat -s 看协议层异常:

bash
netstat -s | grep -iE "(retrans|drop|reject|fail)"
# segments retransmitted: 重传段数
# bad segments: 异常段
# resets sent: 发送的 RST

应用层丢包:看应用日志的请求成功率,如果应用层都没收到请求 → 网络层丢;如果收到了但响应失败 → 应用层问题。

应用偶发"暂时性失败,几秒后自动好"

现象:应用日志报 Connection reset by peerConnection timed out,但不连续——大约几分钟出现一次,持续几秒,然后恢复。

可能的根因 + 判定:

可能判定方式
上游服务实例滚动重启上游运维确认部署时间
防火墙状态表 idle 超时清理cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established(默认 5 天,但中间防火墙可能短得多)
中间网络设备主备切换mtr 长跑,看是否有瞬时丢包
客户端连接池中的连接已被服务端断开,但客户端不知道半开连接,见下一节
临时端口耗尽`ss -ant

半开连接(half-open)

现象:客户端连接池中的连接看起来正常,但发送请求时 hang 几十秒后报错。

根因:客户端持有的 TCP 连接,服务端那侧已经断了(进程重启、主备切换、防火墙清理),但服务端发的 FIN/RST 没到达客户端(防火墙在中间静默丢弃),客户端不知道连接已死,继续往这个"死"连接上写数据,直到内核 TCP 重传超时。

判定:在客户端机器上抓包,看出去的包是否收到任何 ACK:

bash
tcpdump -i any -nn host <服务端IP> and port <>
# 看到本机发了 PSH 但对方完全不回 ACK = 半开

处理:

  • 应用层启用 keepalive——HTTP client 配置 idle timeout、连接池定期校验连接健康度(PING 探测)
  • TCP 层 keepalive——net.ipv4.tcp_keepalive_time 默认 7200(2 小时),太长。客户端机器可以调到几分钟:
bash
echo "net.ipv4.tcp_keepalive_time = 300" >> /etc/sysctl.d/99-tcp.conf
echo "net.ipv4.tcp_keepalive_intvl = 30" >> /etc/sysctl.d/99-tcp.conf
echo "net.ipv4.tcp_keepalive_probes = 5" >> /etc/sysctl.d/99-tcp.conf

注意 TCP keepalive 默认不是开启状态,需要应用程序在 socket 上调用 setsockopt(SO_KEEPALIVE) 才会启用。Java、Python、Go 的高层 HTTP client 通常需要显式配置。

九、tcpdump:数据包级排查

抓包是最后的手段——前面所有层都看不出问题,且现象明确(具体哪个连接、哪个端口),才上抓包。原因:

  • 信息量大,解读耗时
  • 生产抓包可能捕获到敏感数据(密码、token)
  • 大流量场景下抓包文件迅速膨胀(几分钟几个 GB)

标准用法

bash
tcpdump -i eth0 host 10.0.0.1                       # 与某 IP 通信
tcpdump -i eth0 port 80                              # 特定端口
tcpdump -i eth0 host 10.0.0.1 and port 443           # 多条件 AND
tcpdump -i eth0 'host 10.0.0.1 and (port 80 or port 443)'   # 复合条件
tcpdump -i any -nn port 443                          # -i any 所有接口, -nn 不解析端口和主机名
tcpdump -i eth0 -c 100 port 53                       # 抓 100 个包后自动停止
tcpdump -i eth0 -w /tmp/debug.pcap host 10.0.0.1     # 保存到文件,Wireshark 分析
tcpdump -r /tmp/debug.pcap                            # 读取已保存的文件
tcpdump -i eth0 -A port 80                           # -A 显示 ASCII 内容(HTTP 明文)
tcpdump -i eth0 -X port 80                           # -X 同时显示 hex 和 ASCII

生产抓包的关键纪律

必须设过滤条件、必须设上限。不要写无条件的 tcpdump -i any -w file.pcap——几秒钟就是 GB 级文件:

bash
# 限制时间(秒)
timeout 30 tcpdump -i eth0 -w /tmp/debug.pcap host 10.0.0.1

# 限制包数
tcpdump -i eth0 -c 1000 -w /tmp/debug.pcap host 10.0.0.1

# 限制文件大小(MB),滚动覆盖
tcpdump -i eth0 -C 100 -W 5 -w /tmp/debug.pcap host 10.0.0.1
# 单文件 100MB,最多 5 个文件循环,即最多占 500MB

解读输出的关键字段

10:23:45.123456 IP 10.0.0.5.34567 > 192.168.1.10.80: Flags [S], seq 1234567890, win 64240, options [mss 1460,sackOK,TS val 123 ecr 0,nop,wscale 7], length 0

逐段:

  • 10:23:45.123456 — 时间戳(微秒精度)
  • IP — IPv4 协议
  • 10.0.0.5.34567 > 192.168.1.10.80 — 源 IP.端口 → 目的 IP.端口
  • Flags [S] — TCP 标志位:S=SYN, S.=SYN-ACK, .=ACK, P.=PUSH-ACK, F.=FIN-ACK, R=RST
  • seq 1234567890 — 序列号
  • win 64240 — 接收窗口
  • length 0 — payload 长度

排查 TCP 握手问题时,看 Flags 序列:

  • 客户端 → 服务端:[S] → 服务端没回:网络/防火墙问题
  • 服务端 → 客户端:[R](RST):端口未监听
  • 服务端 → 客户端:[S.] 但客户端没回 [.]:返程路径问题

用 ngrep 做应用层抓包

排查 HTTP 明文流量时,ngrep 比 tcpdump 更直观——它直接显示 ASCII 内容:

bash
yum install ngrep -y
apt install ngrep -y

# 抓所有 80 端口的 HTTP 流量,显示请求/响应内容
ngrep -W byline -d eth0 port 80

ngrep 适合排查"HTTP 请求实际发的是什么 / 响应实际是什么",尤其当应用日志记录不完整时。

十、易踩的陷阱与判定速查

现象根因直接判定
ping 不通,但业务能用对方禁了 ICMPcurl / nc -zv 测业务端口
ping 通,业务不通业务端口被防火墙单独拦截nc -zvcurl -v
Connection refused 立即返回包到对方,端口无服务服务端 ss -lntp
Connection timed out 几十秒返回包到不了对方mtr 看路径
mtr 中间跳 100% 丢包ICMP 限速,中间设备不回应看终点是否丢包,终点正常即无问题
dig 出来对,但应用连旧 IP/etc/hosts 有旧记录getent hostsdig 对比
大文件传输卡死,小请求 OK路径 MTU 黑洞ping -M do -s 1472 探测
高并发后无法新建连接TIME_WAIT 堆积耗尽本地端口ss -s 看连接数,扩大 ip_local_port_range
进程的 CLOSE_WAIT 持续增长应用代码漏 close()ss -antp state close-wait 看进程
偶发连接重置,无规律半开连接 / conntrack 超时 / 中间设备超时清理看抓包是否有 RST、是否有未回的 PSH
大量 SYN_RECVSYN flood 或正常流量打满半连接队列tcp_max_syn_backlog 调大、开 syncookies
netstat 看不到某连接,ss 能看到net-tools 版本旧,某些状态不显示统一用 ss
curl host:port 报 "no route to host"路由层不通(IPv6 优先时常见)ip route get <IP> 看路由