适用场景
线上服务出现间歇性超时、请求延迟抖动、连接偶发重置,但应用日志没有明显报错,CPU 总使用率也不高。进一步观察会发现某几颗 CPU 的 si 软中断占用很高,网卡统计里存在 rx_dropped、rx_missed_errors 或 ring buffer 溢出。这类问题常见于高并发网关、Nginx 入口机、日志采集节点、Kubernetes Node、四层代理和数据库代理节点。
本文以 Linux 服务器网卡接收方向为例,整理一套可直接落地的排查和治理方法。
现象描述
典型现象包括:
- 业务侧看到接口偶发超时,重试后又恢复。
top里整体 CPU 不高,但某个 CPU 的%si很高。sar -n DEV显示网卡流量没有打满带宽,但丢包计数增长。- Nginx、Envoy、Java 应用等入口服务没有明显慢 SQL 或下游错误。
- 抓包时不一定能完整看到异常,因为包可能在内核更早的位置已经被丢弃。
先不要只盯着应用线程池。接收方向丢包经常发生在“网卡硬件队列 -> 驱动 ring buffer -> NAPI/软中断 -> 协议栈 backlog -> socket buffer”这一条链路上。
可能原因
常见原因有以下几类:
- 单队列或少数 RX 队列过热,流量哈希不均,导致某个 CPU 处理不过来。
- 网卡 ring buffer 太小,突发流量进入时驱动队列很快被填满。
- RPS/RFS、IRQ 亲和性配置不合理,软中断集中在少数 CPU。
netdev_max_backlog太小,协议栈 backlog 在突发流量下溢出。- 应用读取 socket 不及时,接收缓冲区堆积,最终反压到内核网络栈。
- 虚拟化环境中 vNIC 队列数不足,或者宿主机层面存在 CPU steal、网络限速。
排查思路
排查时建议按“是否真的丢包 -> 丢在哪里 -> 为什么集中在某些 CPU -> 怎么验证治理效果”的顺序推进。
1. 确认网卡和系统层面是否丢包
先找到业务网卡名称:
ip -br addr
ip route get 8.8.8.8
查看网卡统计:
ip -s link show dev eth0
ethtool -S eth0 | egrep -i 'rx.*drop|rx.*miss|rx.*error|timeout|fifo|buffer|queue'
关键字段怎么看:
RX dropped:内核或驱动层接收方向丢弃数量,不同驱动含义略有差异。rx_missed_errors:网卡来不及把包放入 ring buffer,常见于突发流量或 ring 太小。rx_no_buffer_count:驱动没有可用 buffer 接收新包。rx_queue_*_drops:具体 RX 队列上的丢包,可用于判断是否队列不均。
建议连续观察增长速度,而不是只看累计值:
watch -n 1 "ip -s link show dev eth0 | sed -n '/RX:/,+2p'; ethtool -S eth0 | egrep -i 'rx.*drop|rx.*miss|rx_no_buffer|queue.*drop'"
如果计数持续增长,并且与业务超时窗口吻合,基本可以确认不是纯应用层问题。
2. 查看软中断是否集中在少数 CPU
查看每颗 CPU 的软中断变化:
watch -n 1 "cat /proc/softirqs | egrep 'CPU|NET_RX|NET_TX'"
NET_RX 增长特别快的 CPU,就是接收方向网络包主要被处理的位置。如果只有 CPU0 或少数几颗 CPU 增长明显,而其他 CPU 很空,说明负载没有摊开。
再结合 CPU 视角观察:
mpstat -P ALL 1
top -H -p $(pidof nginx | tr ' ' ',')
mpstat 中 %soft 或 %si 高,代表 CPU 时间消耗在软中断处理上。此时应用进程自身 CPU 可能不高,但请求已经在内核网络栈里排队。
3. 检查网卡队列和中断分布
查看网卡通道数:
ethtool -l eth0
输出里的 Combined 或 RX 表示当前队列数量。多队列网卡一般应该让队列数和 CPU 核数、业务流量规模匹配。
查看中断分布:
grep -i eth0 /proc/interrupts
如果某个 IRQ 只集中在一个 CPU 上增长,说明硬中断入口已经不均。很多发行版会运行 irqbalance 自动分配,但在高流量网关上仍建议人工核对。
systemctl status irqbalance
如果禁用了 irqbalance,需要确认是否有明确的 CPU 亲和性规划,否则容易出现单核软中断瓶颈。
4. 检查协议栈 backlog 是否溢出
查看内核网络统计:
nstat -az | egrep 'TcpExtListenOverflows|TcpExtListenDrops|IpInDiscards|IpInReceives|UdpInErrors|UdpRcvbufErrors'
接收方向 backlog 的历史丢包也可以从 /proc/net/softnet_stat 观察。下面命令把十六进制字段转换成人类可读格式:
awk '{printf "cpu=%d processed=%d dropped=%d time_squeeze=%d\n", NR-1, strtonum("0x"$1), strtonum("0x"$2), strtonum("0x"$3)}' /proc/net/softnet_stat
关键字段:
processed:该 CPU 处理的网络包数量。dropped:进入协议栈 backlog 时被丢弃的包数量。time_squeeze:软中断预算不够,被迫把剩余包留到后续处理,持续增长说明处理压力较大。
如果 dropped 或 time_squeeze 在故障窗口持续增长,就要重点看 backlog、队列分布和软中断预算。
定位示例
某台 Nginx 入口机出现 1% 左右的请求超时。应用日志只有少量 upstream timed out,后端服务没有异常。
第一步观察网卡:
ip -s link show dev eth0
ethtool -S eth0 | egrep -i 'rx.*drop|rx.*miss|rx_no_buffer'
发现 rx_missed_errors 每分钟增长几千。
第二步看软中断:
cat /proc/softirqs | egrep 'CPU|NET_RX'
mpstat -P ALL 1
发现 CPU2 的 NET_RX 远高于其他 CPU,%soft 长时间超过 70%,但整机 CPU 平均只有 25%。
第三步看网卡队列:
ethtool -l eth0
grep -i eth0 /proc/interrupts
发现当前只有 1 个 combined queue,中断也集中在 CPU2。问题根因是入口流量上升后,单 RX 队列无法及时处理突发包,最终出现 ring buffer 溢出和协议栈排队。
修复方案
1. 增大网卡 ring buffer
先查看当前和最大值:
ethtool -g eth0
如果 Pre-set maximums 支持更大值,可以调整:
ethtool -G eth0 rx 4096 tx 4096
注意:ring buffer 不是越大越好。它能吸收突发流量,但过大也可能增加排队延迟。入口网关可以先从 1024、2048、4096 分阶段压测和观察。
2. 增加多队列
查看最大队列数:
ethtool -l eth0
调整 combined queue:
ethtool -L eth0 combined 8
建议队列数不要盲目等于 CPU 总核数。对于 8 到 16 核的网关,可以先设置为 4 或 8,然后观察 NET_RX 是否更均衡、延迟是否下降。
3. 配置 RPS 分散软中断
如果网卡硬件队列较少,可以通过 RPS 把接收包分散到多个 CPU 处理。下面示例把 rx-0 分散到 CPU0-7:
printf ff > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
for f in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 4096 > "$f"; done
rps_cpus 是 CPU 位图,ff 表示低 8 个 CPU。生产环境需要结合 NUMA 和业务进程绑定关系配置,避免跨 NUMA 节点带来额外内存访问成本。
4. 调整 backlog 和 socket buffer
临时调整:
sysctl -w net.core.netdev_max_backlog=250000
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728
持久化写入:
cat >/etc/sysctl.d/99-network-rx-tuning.conf <<'EOF'
net.core.netdev_max_backlog = 250000
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
EOF
sysctl --system
netdev_max_backlog 用于控制每个 CPU 输入包队列的最大长度。它能缓解突发流量,但如果应用处理能力不足,只增大 backlog 会把丢包变成更长的排队延迟。
5. 固化启动配置
ethtool -G 和 ethtool -L 调整通常重启后会丢失。可以用 systemd 固化:
[Unit]
Description=Tune eth0 queue and ring
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/ethtool -G eth0 rx 4096 tx 4096
ExecStart=/usr/sbin/ethtool -L eth0 combined 8
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
保存为 /etc/systemd/system/eth0-tuning.service 后执行:
systemctl daemon-reload
systemctl enable --now eth0-tuning.service
验证方法
修复后至少观察以下指标:
watch -n 1 "cat /proc/softirqs | egrep 'CPU|NET_RX'; ethtool -S eth0 | egrep -i 'rx.*drop|rx.*miss|rx_no_buffer'"
验证标准:
NET_RX在多个 CPU 上增长更均衡。rx_missed_errors、rx_no_buffer_count、rx_queue_*_drops不再持续增长。/proc/net/softnet_stat中dropped和time_squeeze增长放缓或停止。- 业务入口 P95/P99 延迟下降,超时率恢复到正常水位。
- 调整后没有出现明显 CPU 上升、跨 NUMA 抖动或平均延迟变长。
预防措施
- 把网卡丢包、软中断、
softnet_stat纳入监控,而不是只监控带宽。 - 高流量入口机上线前压测队列数、ring buffer、RPS 配置。
- 保留
ethtool -S的定时采样,故障复盘时能看到丢包开始增长的时间点。 - 容器节点要同时关注宿主机网卡、veth、容器应用三层指标。
- 调整网络参数后记录变更单,避免重启、驱动升级或镜像迁移后配置丢失。
总结
Linux 接收方向丢包不一定是带宽打满,也不一定是应用进程 CPU 高。很多故障的关键线索在网卡 ring buffer、RX 队列、中断分布、软中断和协议栈 backlog 上。排查时先确认丢包计数是否增长,再定位是硬件队列、驱动、软中断还是 socket 读取能力的问题。治理时优先让负载分散、队列合理、突发可吸收,并用业务延迟和内核计数共同验证效果。
Discussion
评论