网络问题是线上故障中最难定位的一类。症状千变万化——P99 延迟突然抖动、某个服务间歇性超时、容器之间偶发丢包——而根因可能藏在协议栈的任何一层:网卡驱动的 ring buffer 溢出、内核 backlog 队列打满、TCP 重传引发的滑动窗口收缩、iptables 规则误命中,乃至 NUMA 拓扑导致的中断不均衡。本文是本系列第十篇,聚焦于工具链与实战:从 ss 的每个输出字段到 bpftrace 脚本,从 /proc/net 的原始数字到三个完整的排查案例,构建一套系统化的网络诊断方法论。
一、网络问题分类与排查框架 1.1 问题三分类 在动手之前,先把问题分类,可以大幅缩小排查范围:
连通性问题(Connectivity) :两端完全不通,ping 无回应,TCP 连接无法建立。根因通常在路由、防火墙、ARP/ND 解析失败或物理链路故障。
性能问题(Performance) :能连通但慢,表现为吞吐低、延迟高或抖动大。根因多在 TCP 拥塞控制参数不当、重传率过高、缓冲区过小或 CPU/中断不均衡。
应用层问题(Application Layer) :底层网络正常,但应用层报错,如 HTTP 502/504、gRPC deadline exceeded。根因往往是 backlog 队列满、TIME_WAIT 端口耗尽、连接池配置不当或 TLS 握手超时。
1.2 系统化排查流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 问题报告 │ ▼ ┌─────────────────────────────────┐ │ 1. 确认问题范围 │ │ - 单点 or 全量? │ │ - 单向 or 双向? │ │ - 特定时间段? │ └────────────┬────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ 2. 收集宏观指标 │ │ ss -s / nstat -az │ │ /proc/net/sockstat │ │ ethtool -S eth0 │ └────────────┬────────────────────┘ │ ┌─────────┴──────────┐ │ │ ▼ ▼ 连通性异常 性能/应用异常 │ │ ▼ ▼ ping/traceroute ss -tienpm (socket 详情) arp -n nstat (重传/超时计数) ip route show tcpdump 抓包分析 iptables -L -v bpftrace 精确追踪 │ │ └─────────┬──────────┘ │ ▼ ┌─────────────────────────────────┐ │ 3. 定位协议栈层次 │ │ L1/L2: ethtool, ip link │ │ L3: ip route, conntrack │ │ L4: ss, tcp 状态机 │ │ App: strace, lsof │ └────────────┬────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ 4. 根因确认与修复 │ │ - 内核参数调整 (sysctl) │ │ - 驱动/固件更新 │ │ - 应用配置修改 │ └─────────────────────────────────┘
二、ss 命令深度解析 ss 是 netstat 的现代替代品,直接读取内核的 socket 数据结构,速度更快、信息更丰富。核心命令:
2.1 Recv-Q 与 Send-Q 的双重语义 这两个字段在不同 socket 状态下含义截然不同,是理解内核队列模型的关键:
状态
Recv-Q 含义
Send-Q 含义
LISTEN
全连接队列(accept queue)当前长度
全连接队列最大长度(backlog)
ESTABLISHED
已收到但应用尚未 read() 的字节数
已发送但尚未被对端 ACK 的字节数
LISTEN 状态 :当 Recv-Q 接近 Send-Q 时,说明全连接队列即将打满。内核会开始丢弃新完成的三次握手连接,客户端表现为连接超时。此时需要扩大 net.core.somaxconn 并让应用更快地调用 accept()。
ESTABLISHED 状态 :Recv-Q 非零说明应用读取速度跟不上网络接收速度(读慢写快场景);Send-Q 持续增大说明对端接收窗口收缩或网络拥塞,数据在发送缓冲区堆积。
2.2 skmem 字段逐字段解析 skmem 展示 socket 的内存使用全景,格式如下:
1 skmem:(r:0,rb:87380,t:0,tb:16384,f:1024,w:0,o:0,bl:0,d:0)
字段
内核变量
含义
r:<rcv_alloc>
sk->sk_rmem_alloc
接收队列中已分配的 skb 内存总量(字节),即实际占用的接收缓冲区
rb:<sk_rcvbuf>
sk->sk_rcvbuf
接收缓冲区上限,由 net.ipv4.tcp_rmem[2] 或 SO_RCVBUF 设置
t:<snd_alloc>
sk->sk_wmem_alloc
发送队列中已分配但尚未释放的 skb 内存(含飞行中的数据)
tb:<sk_sndbuf>
sk->sk_sndbuf
发送缓冲区上限,由 net.ipv4.tcp_wmem[2] 或 SO_SNDBUF 设置
f:<fwd_alloc>
sk->sk_forward_alloc
预分配但尚未使用的内存额度,来自 proto 的内存预算机制
w:<wmem_alloc>
sk->sk_wmem_queued
发送队列(未发送 + 已发送未 ACK)中的 skb 总内存
o:<opt_mem>
sk->sk_optmem_alloc
socket 选项(如 IP_OPTIONS、cmsg)消耗的内存
bl:<back_log>
sk->sk_backlog.len
软中断上下文来不及处理、暂存在 backlog 队列中的数据量
d:<drops>
sk->sk_drops
因缓冲区满等原因丢弃的包计数(累计)
诊断要点 :当 r 接近 rb 时,说明接收缓冲区即将满,下一个数据包将触发 sk_rmem_schedule 失败进而丢包;当 bl 持续非零时,说明内核软中断处理不及时,可检查 /proc/net/softnet_stat 中的 time_squeeze 列。
2.3 TCP 内部信息字段解析 1 2 3 4 5 6 7 cubic wscale:7,7 rto:204 rtt:4.5/0.75 ato:40 mss:1448 pmtu:1500 cwnd:10 ssthresh:7 bytes_sent:12345 bytes_retrans:0 bytes_acked:12345 bytes_received:9876 segs_out:100 segs_in:95 data_segs_out:80 data_segs_in:75 send 257.1Mbps lastsnd:10 lastrcv:20 lastack:15 pacing_rate 514.3Mbps delivery_rate 257.1Mbps delivered:80 app_limited busy:100ms retrans:0/1 dsack_dups:0 reordering:3 rcv_rtt:4.5 rcv_space:14480 rcv_ssthresh:87380 minrtt:3.2
字段
含义与来源
cubic
当前使用的拥塞控制算法,对应 tp->icsk_ca_ops->name
wscale:7,7
<本端窗口缩放因子>,<对端窗口缩放因子>,来自 tp->rx_opt.snd_wscale 和 rcv_wscale。实际窗口大小 = 报文窗口字段 × 2^wscale
rto:204
当前重传超时值(毫秒),由 RTT 和抖动计算:RTO = SRTT + 4×RTTVAR,下限 200ms
rtt:4.5/0.75
<平滑RTT(ms)>/<RTT方差(ms)>,对应内核 tp->srtt_us>>3 和 tp->mdev_us>>2
ato:40
ACK 延迟超时(ms),TCP 延迟确认定时器,通常 40ms,对应 icsk->icsk_ack.ato
mss:1448
当前有效 MSS(本端发送的最大段大小),受 MTU、PMTU 和对端通告 MSS 共同限制
cwnd:10
拥塞窗口(段数),tp->snd_cwnd,限制飞行中的数据量为 cwnd × mss 字节
ssthresh:7
慢启动阈值(段数),tp->snd_ssthresh,超过此值进入拥塞避免阶段
bytes_retrans
累计重传字节数,tp->bytes_retrans
retrans:0/1
<当前飞行中的重传段数>/<累计重传段数>,前者非零说明正在重传
pacing_rate
TCP Pacing 发送速率,由 BBR 或 FQ 调度器控制,避免突发
delivery_rate
最新测量到的实际交付速率,用于 BBR 带宽估算
rcv_rtt
接收端测量的 RTT(ms),用于接收缓冲区自动调优
rcv_space
接收端滑动窗口大小,由接收速率动态调整
2.4 timer 字段
格式为 (<timer_type>,<expires>,<retransmits>):
timer_type
含义
on
重传定时器运行中
keepalive
Keepalive 探测定时器
timewait
TIME_WAIT 定时器(2MSL)
persist
零窗口探测定时器
off
无定时器
expires 是定时器剩余时间;retransmits 是已重传次数,达到 tcp_retries2(默认 15)后连接将被强制关闭。
三、/proc/net 虚拟文件深度解析 3.1 /proc/net/tcp 字段解析
各列含义:
列
说明
sl
socket 在哈希桶中的槽位编号
local_address
本地 IP:Port,十六进制,小端序 :0F02000A = 10.0.2.15,0016 = 22
rem_address
远端 IP:Port,同上编码方式
st
TCP 状态,十六进制枚举(见下表)
tx_queue:rx_queue
发送队列字节数:接收队列字节数(十六进制)
tr:tm->when
重传定时器是否在运行(0/1):定时器剩余 jiffies
retrnsmt
已重传次数
uid
socket 所属用户 uid
inode
socket 对应的 inode 号,可通过 ls -la /proc/<pid>/fd/ 映射到进程
TCP 状态枚举表 :
十六进制
状态名
01
ESTABLISHED
02
SYN_SENT
03
SYN_RECV
04
FIN_WAIT1
05
FIN_WAIT2
06
TIME_WAIT
07
CLOSE
08
CLOSE_WAIT
09
LAST_ACK
0A
LISTEN
0B
CLOSING
1 2 awk 'NR>1 {print $4}' /proc/net/tcp | sort | uniq -c | sort -rn
3.2 /proc/net/sockstat 1 2 3 4 5 6 7 cat /proc/net/sockstat
字段
含义
TCP: inuse
当前使用中的 TCP socket 数
orphan
孤儿 socket 数:已调用 close() 但尚未完成四次挥手,不属于任何进程。过多会消耗内存,受 net.ipv4.tcp_max_orphans 限制
tw
TIME_WAIT socket 数,过多时检查 tcp_tw_reuse 参数
alloc
已分配(含未绑定)的 TCP socket 总数
mem
TCP socket 占用的内存页数,乘以页大小(通常 4KB)得字节数
3.3 nstat 关键指标解析 1 nstat -az | grep -E 'Retrans|Timeout|OFO|Backlog|Listen|Drop'
指标
内核来源
告警意义
TcpExtTCPFastRetrans
快速重传触发次数(收到 3 个重复 ACK),相对正常
持续增长说明网络存在随机丢包
TcpExtTCPTimeouts
RTO 超时重传次数
比 FastRetrans 严重,说明丢包率高或 RTT 剧烈抖动
TcpExtTCPOFOQueue
收到乱序包放入 OFO 队列的次数
大量乱序可能触发不必要的重传
TcpExtTCPBacklogDrop
socket 的 backlog 队列满而丢弃的包数
应用处理 socket 太慢,增大 backlog 或优化应用
TcpExtListenDrops
全连接队列满时丢弃 SYN 的次数
需增大 somaxconn 或加快 accept()
TcpExtListenOverflows
全连接队列溢出次数(与 ListenDrops 相似但计数时机不同)
同上
TcpExtTCPSACKDiscard
SACK 信息被丢弃(通常因重复或无效)
—
TcpExtTCPAbortOnTimeout
因连接超时(达到最大重试次数)而中止的连接数
说明有连接长时间无响应
TcpExtTCPSynRetrans
SYN 包重传次数
持续增长说明服务端 backlog 满或存在 SYN flood
3.4 /proc/net/snmp 关键字段 1 cat /proc/net/snmp | grep -E '^Tcp|^Ip|^Udp'
重要字段:
1 Tcp: ... RetransSegs ... InErrs ... OutRsts ...
字段
含义
Ip/InDiscards
IP 层因内存不足丢弃的入包数
Ip/OutDiscards
IP 层因无路由或资源不足丢弃的出包数
Tcp/ActiveOpens
主动建立连接次数(客户端发起)
Tcp/PassiveOpens
被动建立连接次数(服务端接受)
Tcp/AttemptFails
连接建立失败次数(SYN_SENT/SYN_RECV 中途失败)
Tcp/EstabResets
ESTABLISHED 状态下被 RST 关闭的连接数
Tcp/RetransSegs
重传段总数,除以 OutSegs 得重传率
Tcp/InErrs
收到的错误 TCP 段数(checksum 失败等)
Tcp/OutRsts
发出的 RST 包数,突增说明有异常断连
Udp/RcvbufErrors
UDP 接收缓冲区满丢包数
Udp/SndbufErrors
UDP 发送缓冲区满丢包数
四、tcpdump 高级用法 4.1 BPF 捕获过滤器语法精华 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 tcpdump -i eth0 -nn host 10.0.0.1 and port 8080 tcpdump -i eth0 -nn proto tcp tcpdump -i eth0 -nn udp and port 53 tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0' tcpdump -i eth0 -nn 'tcp[tcpflags] == tcp-syn' tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0' tcpdump -i eth0 -nn 'tcp[14:2] == 0' tcpdump -i eth0 -nn 'tcp[14:2] < 1000 and tcp[14:2] > 0' tcpdump -i eth0 -nn 'icmp[icmptype] == icmp-unreach' tcpdump -i eth0 -nn '(host 10.0.0.1 and port 80) or port 53' tcpdump -i eth0 -nn 'not port 22' tcpdump -i eth0 -nn 'greater 1400'
4.2 TCP 握手异常分析 SYN 重传 :在抓包中看到同一个 (src_ip, src_port, seq_num) 的 SYN 包出现多次,时间间隔通常是 1s、2s、4s(指数退避)。
1 2 3 4 5 tcpdump -i eth0 -nn -w /tmp/syn.pcap 'tcp[tcpflags] == tcp-syn' & tshark -r /tmp/syn.pcap -q -z io,stat ,1,"tcp.flags.syn==1 && tcp.analysis.retransmission"
RST 原因判断 :
1 2 tcpdump -i eth0 -nn -A 'tcp[tcpflags] & tcp-rst != 0'
RST 来源分析:
服务端主动 RST :通常因为对端发来的包不符合预期(如连接已关闭但收到数据),或调用了 SO_LINGER 选项
防火墙/中间盒 RST :RST 包的 TTL 与正常通信包不同,seq number 恰好在窗口边界,可疑
客户端 RST :收到 SYN-ACK 但本地没有对应连接(端口复用冲突),OutRsts 增加
4.3 零窗口与拥塞分析 1 2 3 4 5 6 7 8 9 10 11 tcpdump -i eth0 -nn -w /tmp/full.pcap host 10.0.0.1 and port 8080 tshark -r /tmp/full.pcap -Y "tcp.window_size == 0" -T fields \ -e frame.time -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport tshark -r /tmp/full.pcap -T fields \ -e frame.time_relative -e tcp.window_size -e tcp.analysis.zero_window \ -Y "ip.src == 10.0.0.1"
4.4 典型诊断命令集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 tcpdump -i eth0 -nn -q 2>&1 | pv -l -i 1 > /dev/null tcpdump -i eth0 -nn -S 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0 and not port 22' tcpdump -i eth0 -nn 'udp port 53' -w /tmp/dns.pcap tcpdump -i eth0 -nn 'ip[6:2] & 0x4000 != 0 and greater 1400' tcpdump -r /tmp/full.pcap -nn -q | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head tcpdump -i eth0 -nn 'tcp[tcpflags] == tcp-syn' -A | grep -A2 'Fast Open' tcpdump -i eth0 -nn 'tcp[tcpflags] == tcp-syn' -v 2>&1 | grep -i 'timestamp\|nop'
五、bpftrace 网络诊断脚本集 bpftrace 基于 eBPF,可以在不修改内核的情况下动态追踪任意内核函数,开销极低。以下五个脚本覆盖网络诊断最常见场景。
脚本一:TCP 重传追踪(含源目的 IP:Port) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/usr/bin/env bpftrace /* * 追踪 TCP 重传事件,打印进程、本地端口、远端端口 * 可用于快速确认哪些连接在重传、重传频率 */ kprobe:tcp_retransmit_skb { $sk = (struct sock *)arg0; $lport = $sk ->__sk_common.skc_num; $dport = $sk ->__sk_common.skc_dport; /* dport 在内核中是网络字节序,需要手动转换 */ $dport = ($dport >> 8) | (($dport << 8 ) & 0 xff00); printf("%-6 d %-12 s TCP retransmit: lport=%d dport=%d\n", pid, comm, $lport , $dport ); }
运行:sudo bpftrace retrans.bt,输出示例:
1 2 12345 nginx TCP retransmit: lport=80 dport=54321 12345 nginx TCP retransmit: lport=80 dport=54322
若某个 dport 频繁出现,说明该客户端连接质量差,可结合 ss 查看该连接的 RTT 和重传计数确认。
脚本二:TCP 连接建立延迟分布 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/usr/bin/env bpftrace /* * 测量 tcp_v4_connect() 调用耗时,以直方图展示 * 用于量化 DNS 解析 + 三次握手的端到端延迟 */ kprobe:tcp_v4_connect { @start[tid] = nsecs; } kretprobe:tcp_v4_connect /@start[tid]/ { @connect_lat_us = hist((nsecs - @start[tid]) / 1000 ); delete(@start[tid]); } END { printf("\nTCP connect latency distribution (us):\n"); print(@connect_lat_us); }
输出示例:
1 2 3 4 5 6 @connect_lat_us: [256, 512) 5 |@@ | [512, 1K) 42 |@@@@@@@@@@@@@@@@@@ | [1K, 2K) 89 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [2K, 4K) 23 |@@@@@@@@@ | [4K, 8K) 3 |@ |
延迟集中在 1ms-2ms 为正常本地网络;若出现 [100K, 200K) 的长尾,说明存在严重的连接建立延迟,需检查是否有 SYN 丢包导致的 1s 重传。
脚本三:按进程统计发送/接收字节 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #!/usr/bin/env bpftrace /* * 每 5 秒打印一次各进程的 TCP 收发字节统计 * 快速定位带宽消耗大户 */ kprobe:tcp_sendmsg { /* arg2 是 size_t size,即本次发送请求的字节数 */ @sent_bytes[comm , pid] += arg2; } kprobe:tcp_recvmsg { /* arg2 是 size_t size,即本次接收请求的缓冲区大小 */ @recv_bytes[comm , pid] += arg2; } interval:s:5 { printf ("\n=== Top senders (5s) ===\n" ); print (@sent_bytes); printf ("\n=== Top receivers (5s) ===\n" ); print (@recv_bytes); clear(@sent_bytes); clear(@recv_bytes); }
注意:tcp_sendmsg 的 arg2 是应用请求发送的字节数,不等于实际发出的字节数(受发送缓冲区影响),但在排查带宽使用时已足够准确。
脚本四:Socket 接收队列积压监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #!/usr/bin/env bpftrace /* * 监控各进程的 socket backlog 积压情况 * sk_backlog.len 是软中断来不及处理的包的总大小 * 持续非零说明 CPU 处理 softirq 不及时 */ kprobe:tcp_add_backlog { $sk = (struct sock *)arg0; $backlog_len = $sk ->sk_backlog.len; if ($backlog_len > 0) { @backlog[comm ] = max($backlog_len ); @backlog_hist = hist($backlog_len ); } } interval:s:1 { if (@backlog) { printf ("\n--- Backlog snapshot ---\n" ); print (@backlog); } }
若 @backlog_hist 中出现大量高值,需检查:
napi_schedule 是否不均匀(多队列网卡的 RSS 配置)
ksoftirqd CPU 占用率
/proc/net/softnet_stat 中的 time_squeeze 列是否在增长
脚本五:追踪 TCP 状态变化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/usr/bin/env bpftrace /* * 追踪 TCP 状态机转换,打印每次状态变化 * 对于排查异常断连(直接从 ESTABLISHED 跳到 CLOSE)非常有用 */ kprobe:tcp_set_state { $sk = (struct sock *)arg0; $newstate = (int32)arg1; $oldstate = (int32)$sk ->__sk_common.skc_state; $state_names = ("UNKNOWN" , "ESTABLISHED" , "SYN_SENT" , "SYN_RECV" , "FIN_WAIT1" , "FIN_WAIT2" , "TIME_WAIT" , "CLOSE" , "CLOSE_WAIT" , "LAST_ACK" , "LISTEN" , "CLOSING" , "NEW_SYN_RECV" ); /* 只关注从 ESTABLISHED 开始的断连过程 */ if ($oldstate == 1 || $newstate == 7 || $newstate == 8) { printf ("%-12s pid=%-6d %s -> %s\n" , comm , pid, $state_names [$oldstate ], $state_names [$newstate ]); } }
输出示例:
1 2 3 nginx pid=12345 ESTABLISHED -> FIN_WAIT1 # 正常主动关闭 nginx pid=12345 ESTABLISHED -> CLOSE_WAIT # 对端主动关闭 nginx pid=12345 ESTABLISHED -> CLOSE # 异常!RST 导致
第三行的 ESTABLISHED -> CLOSE 跳过了正常的四次挥手,通常由 RST 触发,需结合 tcpdump 进一步定位 RST 原因。
六、实战案例一:排查 P99 延迟抖动(TCP 重传) 场景 :监控系统显示某微服务 P99 延迟从 50ms 上升到 800ms,P50 正常,怀疑存在 TCP 重传。
步骤一:找出重传多的连接 1 ss -tienp | grep ESTAB | sort -k5 -n | tail -10
重点看 retrans 和 bytes_retrans 字段:
1 2 3 ESTAB 0 0 10.0.0.1:8080 10.0.0.2:54321 cubic wscale:7,7 rto:412 rtt:105/82.5 ato:40 mss:1448 cwnd:4 ssthresh:4 bytes_retrans:204800 retrans:2/45
解读:rtt:105/82.5 说明平均 RTT 105ms 且方差极大(正常应为 4.5/0.75),retrans:2/45 说明累计重传 45 次,ssthresh:4 说明拥塞控制已经将阈值压到很低,吞吐受到严重限制。
步骤二:确认重传计数增长趋势 1 watch -d -n 1 'nstat -az | grep -E "Retrans|Loss|Timeout"'
1 2 3 4 TcpExtTCPFastRetrans 1234 0.0 # 快速重传,网络有随机丢包 TcpExtTCPTimeouts 87 0.0 # RTO 超时重传,更严重 TcpExtTCPSynRetrans 3 0.0 # SYN 重传,建立连接也慢 TcpExtTCPLostRetransmit 12 0.0 # 重传包本身也丢了
TcpExtTCPTimeouts 持续增长是核心告警信号,说明 RTO 触发导致 800ms 的延迟峰值(默认 RTO 下限 200ms,第一次超时等 200ms,若再丢就等 400ms)。
步骤三:抓包确认丢包位置 1 2 3 4 5 6 tcpdump -i eth0 -nn -w /tmp/retrans.pcap \ 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0 or (tcp[14:2] < 1000)' tcpdump -i eth0 -nn -w /tmp/full.pcap -C 100 -W 5 'port 8080'
1 2 tshark -r /tmp/full.pcap -q -z io,stat ,1,"tcp.analysis.retransmission"
步骤四:bpftrace 精确追踪重传触发点 1 2 3 4 5 6 sudo bpftrace -e ' kprobe:tcp_retransmit_skb { @[comm, pid, kstack] = count(); } interval:s:10 { print(@); clear(@); } '
通过内核调用栈可以判断重传是由 RTO 定时器触发(tcp_write_timer_handler)还是由 SACK 驱动的快速重传(tcp_fastretrans_alert)。
步骤五:检查网卡错误统计 1 ethtool -S eth0 | grep -iE 'error|drop|miss|fifo'
1 2 3 rx_dropped: 1523 rx_fifo_errors: 891 tx_errors: 0
rx_fifo_errors 说明网卡 ring buffer 已满,包在驱动层被丢弃,不经过协议栈,因此 nstat 里看不到,但 tcpdump 也抓不到。需要增大 ring buffer:
步骤六:检查 CPU 中断分布 1 cat /proc/interrupts | grep eth0
1 2 3 45: 28000000 0 0 0 PCI-MSI eth0-0 46: 500 1200000 0 0 PCI-MSI eth0-1 47: 1000 0 1800000 0 PCI-MSI eth0-2
发现 eth0-1 和 eth0-2 的中断都压在 CPU 1 和 CPU 2 上,CPU 0 和 CPU 3 几乎没有。调整 IRQ 亲和性:
1 2 3 4 echo "f" > /proc/irq/45/smp_affinity echo "f" > /proc/irq/46/smp_affinitysystemctl restart irqbalance
七、实战案例二:排查 SYN 丢包(连接建立失败) 场景 :新上线的服务在高并发时出现大量连接超时,客户端报 Connection refused 或持续 SYN_SENT。
步骤一:确认全局 SYN 丢包情况 1 netstat -s | grep -iE 'syn|listen|backlog|overflow'
1 2 3456 SYNs to LISTEN sockets dropped 1234 times the listen queue of a socket overflowed
若这两个数字在持续增长(用 watch 监测差值),确认是 backlog 满导致的 SYN 丢包。
步骤二:检查全连接队列占用
1 2 State Recv-Q Send-Q Local Address:Port LISTEN 512 512 0.0.0.0:8080
Recv-Q == Send-Q == 512 说明全连接队列已经打满(Recv-Q = 当前队列长度,Send-Q = 队列最大值)。当队列满时,新完成三次握手的连接会被直接丢弃,客户端的 SYN 需要等到 RTO 超时(1s)后重传。
步骤三:查看当前 backlog 配置 1 2 3 sysctl net.ipv4.tcp_max_syn_backlog net.core.somaxconn
两个值都是 128,远不够用。调整:
1 2 3 sysctl -w net.core.somaxconn=65535 sysctl -w net.ipv4.tcp_max_syn_backlog=65535
重要 :somaxconn 是系统级上限,但应用调用 listen(fd, backlog) 时传入的值才是实际生效的全连接队列大小(取两者中的较小值)。Nginx 对应配置 listen 8080 backlog=65535,Java 应用需修改 ServerSocket 构造参数。
步骤四:检查 SYN Cookie 1 2 sysctl net.ipv4.tcp_syncookies
SYN Cookie 是在半连接队列满时的保护机制:服务端不记录 SYN 状态,而是把连接信息编码进 SYN-ACK 的 ISN 中。优点是防 SYN flood,缺点是无法使用 TCP 选项(SACK、窗口缩放等)。若已开启但仍然大量丢包,说明是全连接队列满的问题,SYN Cookie 此时不起作用。
1 2 3 4 5 nstat -az | grep -i 'cookie'
步骤五:排查 conntrack 表满 如果服务器部署了 iptables NAT 或防火墙规则,连接跟踪表(conntrack)满也会导致新连接被丢弃:
1 2 3 sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
1 2 3 4 5 6 7 8 9 sysctl -w net.netfilter.nf_conntrack_max=524288 sysctl net.netfilter.nf_conntrack_tcp_timeout_time_wait sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
步骤六:修复建议汇总
增大 somaxconn 和 tcp_max_syn_backlog 至少到 65535
更新应用 listen() backlog 参数并重启
扩大 conntrack 表(如使用了 NAT)
检查并优化应用的 accept() 速度(避免 accept 线程成为瓶颈)
开启 tcp_syncookies 作为最后防线
八、实战案例三:容器网络丢包排查 场景 :Kubernetes 集群中,Pod A 访问 Pod B 时出现间歇性丢包(ping 丢包率约 0.5%)。
L1/L2:物理网卡层 1 2 3 4 5 6 7 ethtool -S eth0 | grep -iE 'error|drop|miss|crc|fifo' ip -s link show eth0
dropped 是驱动层丢包(ring buffer 满),missed 是硬件来不及 DMA 的丢包。两者都需扩大 ring buffer 并检查 CPU 中断亲和性。
veth 对层 1 2 3 4 5 6 7 8 POD_PID=$(crictl inspect <container_id> | jq '.info.pid' ) nsenter -t $POD_PID -n ip link show ip -s link show veth3a8b2c1d
veth 对的 dropped 通常因为:
容器内的接收缓冲区满(sk_rcvbuf 耗尽)
iptables 规则丢包(下一步检查)
qdisc 队列满(检查 tc qdisc show dev veth3a8b2c1d)
Bridge / OVS 层 1 2 3 4 5 6 7 8 9 10 bridge -s link bridge fdb show br0 bridge fdb show | grep <pod_mac> ovs-vsctl show ovs-ofctl dump-flows br0 | grep drop
iptables/nftables 层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 iptables -t nat -L -n -v --line-numbers | grep -v "0 0" iptables -t filter -L FORWARD -n -v | grep -v "0 0" iptables -L KUBE-FORWARD -n -v nft list ruleset | grep -A5 'drop\|reject' iptables -t raw -I PREROUTING -s <src_ip> -j TRACE iptables -t raw -I OUTPUT -d <dst_ip> -j TRACE dmesg | grep 'TRACE:' iptables -t raw -D PREROUTING -s <src_ip> -j TRACE
容器内部 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 nsenter -t $POD_PID -n bash ip route show arp -n conntrack -L | wc -l conntrack -S ss -tienm | head -20
常见根因 :
Calico/Flannel 的 IPIPtunnel 或 VXLAN 的 MTU 比物理网卡小 50/20 字节,导致大包触发 IP 分片或 PMTU 黑洞
kube-proxy 的 iptables 规则链过长(超过 10000 条),导致规则匹配 CPU 占用高
conntrack 表在高并发时插入冲突(insert_failed 计数增长),导致包被 DROP
1 2 3 4 ip link show | grep mtu
九、诊断工具速查表
工具
主要用途
核心命令示例
ss
Socket 状态、队列深度、TCP 内部参数
ss -tienpm
nstat
内核网络计数器(比 netstat -s 更快)
nstat -az | grep Retrans
tcpdump
抓包,支持 BPF 过滤
tcpdump -i eth0 -nn -w file.pcap 'port 80'
tshark
tcpdump 离线分析,支持协议解析
tshark -r file.pcap -Y "tcp.analysis.retransmission"
bpftrace
内核动态追踪,低开销 eBPF 脚本
bpftrace -e 'kprobe:tcp_retransmit_skb { printf("%s\n", comm); }'
perf
CPU 性能分析,支持网络子系统
perf stat -e 'net:*' -p <pid>
ethtool
网卡配置和统计
ethtool -S eth0 | grep drop
ip
网络接口、路由、统计
ip -s link show eth0
conntrack
连接跟踪表查询和统计
conntrack -S
tc
流量控制,查看 qdisc 丢包
tc -s qdisc show dev eth0
netstat
连接状态统计(老工具,可被 ss 替代)
netstat -s | grep -i retrans
sar
历史网络统计(sysstat 包)
sar -n DEV 1 10
iptraf-ng
实时流量监控,交互式界面
iptraf-ng -i eth0
mtr
结合 traceroute 和 ping,持续路径探测
mtr --report --tcp -P 80 10.0.0.1
nmap
端口扫描,确认服务监听状态
nmap -sS -p 8080 10.0.0.1
strace
追踪进程系统调用,定位 socket API 调用
strace -e trace=network -p <pid>
lsof
查看进程打开的 socket
lsof -i :8080 -n -P
systemtap
更强大的内核动态追踪(需编译)
stap -e 'probe tcp.sendmsg { ... }'
小结 本文系统地介绍了 Linux 网络诊断的完整工具链和方法论:
分类先于动手 :区分连通性、性能和应用层问题,选择正确的工具切入点,避免盲目抓包
ss 是第一现场 :ss -tienpm 的输出几乎涵盖了 TCP 连接的完整内核状态,能快速定位重传、队列积压、缓冲区不足等问题
计数器是趋势指标 :nstat、/proc/net/snmp 提供全局视角,通过观察计数器的增长速率判断问题的严重程度和类型
tcpdump 提供证据 :当计数器告诉你”有问题”,tcpdump 告诉你”具体是什么包出了问题”
bpftrace 提供根因 :当 tcpdump 看到了现象,bpftrace 深入内核调用栈,精确定位代码路径
逐层排查容器网络 :从物理网卡到 veth,从 bridge 到 iptables,从宿主机到容器内,每层都有专属工具
掌握这套工具链,绝大多数线上网络问题都可以在分钟级定位到根因。下一篇将进入网络性能调优的主题,探讨如何通过内核参数和硬件配置最大化网络吞吐。