适用场景

线上服务出现间歇性超时、请求延迟抖动、连接偶发重置,但应用日志没有明显报错,CPU 总使用率也不高。进一步观察会发现某几颗 CPU 的 si 软中断占用很高,网卡统计里存在 rx_droppedrx_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”这一条链路上。

可能原因

常见原因有以下几类:

  1. 单队列或少数 RX 队列过热,流量哈希不均,导致某个 CPU 处理不过来。
  2. 网卡 ring buffer 太小,突发流量进入时驱动队列很快被填满。
  3. RPS/RFS、IRQ 亲和性配置不合理,软中断集中在少数 CPU。
  4. netdev_max_backlog 太小,协议栈 backlog 在突发流量下溢出。
  5. 应用读取 socket 不及时,接收缓冲区堆积,最终反压到内核网络栈。
  6. 虚拟化环境中 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

输出里的 CombinedRX 表示当前队列数量。多队列网卡一般应该让队列数和 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:软中断预算不够,被迫把剩余包留到后续处理,持续增长说明处理压力较大。

如果 droppedtime_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 -Gethtool -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_errorsrx_no_buffer_countrx_queue_*_drops 不再持续增长。
  • /proc/net/softnet_statdroppedtime_squeeze 增长放缓或停止。
  • 业务入口 P95/P99 延迟下降,超时率恢复到正常水位。
  • 调整后没有出现明显 CPU 上升、跨 NUMA 抖动或平均延迟变长。

预防措施

  1. 把网卡丢包、软中断、softnet_stat 纳入监控,而不是只监控带宽。
  2. 高流量入口机上线前压测队列数、ring buffer、RPS 配置。
  3. 保留 ethtool -S 的定时采样,故障复盘时能看到丢包开始增长的时间点。
  4. 容器节点要同时关注宿主机网卡、veth、容器应用三层指标。
  5. 调整网络参数后记录变更单,避免重启、驱动升级或镜像迁移后配置丢失。

总结

Linux 接收方向丢包不一定是带宽打满,也不一定是应用进程 CPU 高。很多故障的关键线索在网卡 ring buffer、RX 队列、中断分布、软中断和协议栈 backlog 上。排查时先确认丢包计数是否增长,再定位是硬件队列、驱动、软中断还是 socket 读取能力的问题。治理时优先让负载分散、队列合理、突发可吸收,并用业务延迟和内核计数共同验证效果。