前几天线上排查一个典型的网络瓶颈:业务反馈吞吐上不去、P99 延迟偶尔抖到几百毫秒。登上去随手敲了个 top,好家伙,CPU 0 的 %si(软中断)直接拉满到 98%,而其他 31 个核都在“摸鱼”。
这就是典型的单核软中断被打爆,协议栈收包吞吐遇到硬天花板。今天内核喵就带大家顺着调用栈,把网卡多队列、RSS 和软中断这套链路彻底捋顺,拒绝玄学,只看数据。
1. 现象复现:为什么总是 CPU 0 在受罪?
在没有做针对性优化的情况下,默认配置经常会出现软中断倾斜。我们先来看一组真实的监控数据:
$ mpstat -P ALL 1 1
Linux 5.15.0-89-generic (node-01) 03/30/2026 _x86_64_ (32 CPU)
14:32:01 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
14:32:02 all 3.12 0.00 4.50 0.00 0.15 3.20 0.00 0.00 89.03
14:32:02 0 0.00 0.00 2.00 0.00 1.00 96.00 0.00 0.00 1.00
14:32:02 1 4.04 0.00 5.05 0.00 0.00 0.00 0.00 0.00 90.91
14:32:02 2 3.00 0.00 4.00 0.00 0.00 0.00 0.00 0.00 93.00
...
掏出 perf top 抓一下热点函数,映入眼帘的全是网络栈的硬逻辑:
42.15% [kernel] [k] napi_gro_receive
18.30% [kernel] [k] __netif_receive_skb_core
12.45% [kernel] [k] ip_rcv
8.12% [ixgbe] [k] ixgbe_poll
硬件中断(Hard IRQ)只负责在收包时通知 CPU 并调度 NAPI(调用 napi_schedule),随后便开启轮询模式,实际的大头消耗全在软中断 NET_RX_SOFTIRQ 驱动的 ksoftirqd/x 或直接借用中断上下文的收包循环中。如果网络包的中断全扔给一个核心,这个核心的 Ring Buffer 很快就会溢出丢包。
2. 第一步:确认硬件是否支持多队列与 RSS
如果你的网卡是物理万兆网卡(比如 Intel 82599、Mellanox ConnectX 系列)或者主流云厂商开启了多队列的虚拟网卡(virtio-net),第一步必须确认队列数是否开满。
用 ethtool -l 查看队列上限:
$ sudo ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 0
Combined: 8 # <-- 硬件最多支持 8 组组合队列
Current hardware settings:
RX: 0
TX: 0
Other: 0
Combined: 1 # <-- 坑点:当前居然只开了 1 个队列!
如果只配置了 1,无论你怎么配亲和性,它也只能跑在一个 CPU 上。直接拉满:
$ sudo ethtool -L eth0 combined 8
接着检查 RSS(Receive Side Scaling)哈希分布:
$ sudo ethtool -x eth0
RX flow hash indirection table for eth0 with 8 RX ring(s):
0: 0 1 2 3 4 5 6 7
8: 0 1 2 3 4 5 6 7
...
确保 Flow Hash 表均匀打散在所有的 Ring 队列上。
3. 第二步:干掉 irqbalance,手动绑定中断亲和性
系统默认运行的 irqbalance 守护进程在某些高并发网络场景下是个“坑爹”的存在——它在不同 CPU 之间调度 IRQ 会导致 CPU 缓存(L1/L2 Cache)剧烈抖动,而且算法偶尔还会抽风。
针对高吞吐后端节点,建议直接停用 irqbalance,手动实现 1 队列绑定 1 核心。
$ sudo systemctl stop irqbalance
$ sudo systemctl disable irqbalance
找到网卡对应的中断号:
$ grep eth0 /proc/interrupts
128: 14520391 0 0 PCI-MSI 524288-edge eth0-TxRx-0
129: 0 14389201 0 PCI-MSI 524289-edge eth0-TxRx-1
130: 0 0 14412093 PCI-MSI 524290-edge eth0-TxRx-2
...
通过配置 /proc/irq/{IRQ}/smp_affinity 来绑定核心。注意:这里的值是十六进制掩码(Bitmask)。例如:
- 绑定 CPU 0:
01 - 绑定 CPU 1:
02 - 绑定 CPU 2:
04 - 绑定 CPU 3:
08
写一个自动化的绑定脚本 bind_irq.sh:
#!/usr/bin/env bash
IFACE="eth0"
# 获取该网卡所有的中断号
IRQS=$(grep -E "${IFACE}" /proc/interrupts | awk -F ':' '{print $1}' | tr -d ' ')
# 避开 CPU 0(留给系统进程),从 CPU 1 开始绑定
CPU=1
for IRQ in ${IRQS}; do
# 计算十六进制掩码 (1 << CPU)
MASK=$(printf "%x" $((1 << CPU)))
echo "Binding IRQ ${IRQ} to CPU ${CPU} (mask: 0x${MASK})"
echo "${MASK}" > /proc/irq/${IRQ}/smp_affinity
CPU=$((CPU + 1))
done
4. 终极救场:硬件队列不够?开启软件级 RPS/RFS
有时候我们在某些轻量级云服务器(例如某些只给了单队列的 ECS)上做调优,硬件层面只有一个中断队列,这时候怎么办?
内核提供了软件层面的模拟:RPS(Receive Packet Steering) 和 RFS(Receive Flow Steering)。它在单队列硬中断触发后,利用协议栈将数据包的 Hash 算出来,然后通过软中断分发给指定的其他 CPU 核心去解析。
查看当前网卡队列的 rps_cpus:
$ cat /sys/class/net/eth0/queues/rx-0/rps_cpus
00000000
如果你的机器是 4 核,想让 4 个 CPU 一起分摊软中断计算,掩码就是 f(二进制 1111):
$ echo "f" | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus
如果要追求极致的 Cache 局部性,还可以打开 RFS,让处理网络包的 CPU 和处理该 Socket 业务的 CPU 保持一致:
# 全局最大流表数
sudo sysctl -w net.core.rps_sock_flow_entries=32768
# 单队列流表数
echo 4096 | sudo tee /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
5. 调优成果验证:用 perf 和 mpstat 验收
调优完成后,再次压测,重新看 mpstat:
$ mpstat -P ALL 1 1
14:45:10 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
14:45:11 all 4.50 0.00 5.10 0.00 0.20 12.50 0.00 0.00 77.70
14:45:11 0 1.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 98.00
14:45:11 1 2.00 0.00 4.00 0.00 0.20 12.10 0.00 0.00 81.70
14:45:11 2 2.00 0.00 3.90 0.00 0.20 12.30 0.00 0.00 81.60
14:45:11 3 1.90 0.00 4.10 0.00 0.20 12.40 0.00 0.00 81.40
...
用追踪工具看看软中断是否真正被打散:
$ sudo perf record -e irq:softirq_entry -a sleep 2
[ perf record: Woken up 8 times to write data ]
[ perf record: Captured and wrote 1.842 MB perf.data (14210 samples) ]
$ sudo perf report --sort cpu --stdio
# Overhead CPU
# ........ ...
12.65% 001
12.58% 002
12.54% 003
12.49% 004
12.48% 005
12.46% 006
12.42% 007
12.38% 008
每个分配的 CPU 处理软中断的比例都在 12% 左右,完全平铺开来。原本堆积在单核上的丢包计数(/proc/net/dev 里的 drop)彻底归零,P99 延迟也直接回落到了平稳基线。
总结
- 先看拓扑:网卡支持物理多队列优先用多队列(
ethtool -L),不支持再考虑软件层面的 RPS/RFS。 - 拒绝乱动:高网络吞吐机器直接关掉
irqbalance,手动实现 1 队列绑定 1 独立的物理核心。 - 避开 CPU 0:CPU 0 往往要承担定时器滴答、部分内核线程驱动等杂活,尽量将网络硬中断绑到 CPU 1 及以后的核心。
- 一切以数据说话:改完参数,掏出
mpstat和perf验一遍才是合格调优工程师的素养。
License: CC BY-NC 4.0
Updated 13 hours ago
Was this article helpful? Give it a like.
0 comments


