在高并发(尤其是海量小包网络吞吐)的场景下,Linux 服务器的 CPU 使用率中经常会出现 si (softirq,软中断) 占比极高、甚至单核被压死的现象。伴随而来的往往是丢包、延迟飙升以及吞吐量严重下滑。
要彻底解决这个问题,需要从 Linux 内核的网卡收包机制说起,并结合系统级观测工具进行针对性调优。
一、 为什么高并发会导致 softirq 飙高?
在 Linux 的网络接收路径中,网卡与内核的交互经历了两阶段:
[ 网卡收到数据包 ]
│
▼
1. 触发硬件中断 (Hard IRQ) ──► 极其短暂,只做最简单的事:清空网卡寄存器,向内核发起 Poll 请求
│
▼
2. 触发软中断 (Soft IRQ) ──► 耗时较长:执行 NAPI 轮询,将数据包从 Ring Buffer 拷贝并封装成 sk_buff,送入协议栈
在网络并发极高(如每秒数十万甚至数百万 PPS)时,软中断(尤其是 NET_RX)会面临极大的压力:
- 单核处理瓶颈(中断亲和性不均):默认情况下,网卡的硬件中断往往会全部打到某一个 CPU 核心上(通常是 CPU0)。这就导致该核心不断触发
NET_RX软中断。一旦单核被软中断占满(si接近 100%),这个核心就无法处理正常的进程上下文,导致包处理延迟增加,网卡 Ring Buffer 溢出并丢包。 - ksoftirqd 线程过载:当软中断处理函数的执行时间过长,或者一次性处理的包太多时,为了避免单次中断处理完全霸占 CPU,内核会将后续的软中断处理工作移交给内核线程
ksoftirqd/X(X 为 CPU 编号)。如果ksoftirqd频繁运行,说明软中断已经严重过载。 - 小包开销巨大:对于 64 字节的小包,网络协议栈封装
sk_buff的内存分配与释放开销,和处理 1500 字节大包几乎一致。PPS 越高,软中断中花在内存分配、内核协议栈流转上的 CPU 时间就越恐怖。
二、 排查工具箱:定位 softirq 瓶颈
在着手优化前,必须先摸清当前系统的运行现状。
1. 观察软中断的整体分布
直接查看 /proc/softirqs,重点关注 NET_RX(网络接收)和 NET_TX(网络发送):
watch -d -n 1 "cat /proc/softirqs | grep -E 'CPU|NET_'"
- 观察重点:各个 CPU 核心上的
NET_RX数值增长速度是否均衡?如果只有 CPU0 在疯狂增长,其他核心纹丝不动,说明存在中断倾和性倾斜。
2. 查看单核 CPU 的 si 占比
使用 mpstat 工具细化到每个核心:
mpstat -P ALL 1
在输出中观察 %usr、%sys 和 %soft(即 si)。如果某几个核心的 %soft 长期处于 80% 以上,说明这些核心正在超负荷处理软中断。
3. 排查网卡是否有丢包
利用 ethtool 查看物理网卡的统计数据:
ethtool -S eth0 | grep -i -E "drop|fifo|miss|err"
- 如果看到
rx_fifo_errors、rx_missed_errors或rx_no_buffer_rx_drops持续上涨,说明网卡硬件的 Ring Buffer 已经满了,内核软中断收包的速度跟不上物理网卡进包的速度。
三、 硬核调优手段:从硬件到内核
解决 softirq 过高,核心思路只有两个:分流(将压力分散到多核) 和 减压(减少单包处理的开销)。
调优手段 1:开启并配置网卡多队列(RSS)与中断绑定
现代网卡大都支持多队列(Receive Side Scaling, RSS)。硬件多队列可以将不同的网络流分配到不同的硬件接收队列中,从而产生不同的硬件中断,分发到不同的 CPU 核心上处理。
检查网卡队列数:
ethtool -l eth0如果
Combined的最大值(Maximum)大于当前值(Active),说明未完全启用。可以使用以下命令调整:ethtool -L eth0 combined 8 # 设为 8 队列(通常与物理 CPU 核心数一致,避免跨 NUMA)关闭 irqbalance 并手动绑定中断:
系统自带的irqbalance服务在网络高负载时经常“帮倒忙”,导致中断在各个 CPU 间频繁漂移,破坏 CPU 缓存。在高并发场景下,建议关闭 irqbalance 并手动绑定中断。关闭服务:
systemctl stop irqbalance systemctl disable irqbalance手动绑定:通过查看
/proc/interrupts找到网卡队列对应的中断号,然后将对应的 CPU 亲和性掩码写入/proc/irq/{IRQ_NUM}/smp_affinity。例如,将中断绑定到 CPU2(掩码为 4,即二进制 0100):echo 4 > /proc/irq/123/smp_affinity注:业界常用脚本
set_irq_affinity.sh(通常由网卡驱动厂商提供)来自动化完成这个绑定过程。
调优手段 2:启用 RPS 和 RFS(软件多队列)
如果网卡不支持硬件多队列,或者硬件队列数小于 CPU 核心数,可以使用内核提供的软分流机制:RPS (Receive Packet Steering) 和 RFS (Receive Flow Steering)。
- RPS:在单个硬件中断将包收上来后,通过哈希算法将数据包分发给其他 CPU 的 backlog 队列中去处理软中断。
启用 RPS(对所有 CPU 开启分流,假设有 8 个 CPU 核心,掩码为ff):echo "ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus - RFS:在 RPS 的基础上,考虑了应用程序所在的 CPU 核心,尽量把数据包分发到运行该 Socket 应用程序的 CPU 上,提高 CPU 缓存命中率。
配置 RFS 需同时设置全局流表和网卡队列流表:sysctl -w net.core.rps_sock_flow_entries=32768 echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
调优手段 3:调整 NAPI 权重与内核收包预算
在软中断执行期间,内核通过 NAPI 机制在一次中断中轮询读取多个数据包。可以通过调整内核参数,让每次软中断处理更多的数据包,减少上下文切换。
- net.core.netdev_budget:一次软中断里,所有 CPU 能够消费的包的最大数量(默认 300,高并发下可调整为 600 或 1000)。
- net.core.netdev_max_backlog:当每个 CPU 的接收队列(backlog)满时,允许暂存的包的最大数量(默认 1000,高并发下可调整为 5000 或 10000)。
写入 /etc/sysctl.conf:
net.core.netdev_budget = 600
net.core.netdev_max_backlog = 10000
执行 sysctl -p 生效。
调优手段 4:调整网卡 Ring Buffer 大小
Ring Buffer 越大,网卡越不容易因为软中断处理延迟而丢包。
- 查看当前和最大 Ring Buffer:
ethtool -g eth0 - 调整至最大值(假设最大支持 4096):
ethtool -G eth0 rx 4096 tx 4096
调优手段 5:开启硬件卸载(Offloading)
利用网卡硬件来做原本需要 CPU 软中断完成的数据包合并、分片等工作。
- GRO (Generic Receive Offload):将多个接收到的小包在网卡层合并成大包,再递交给 TCP 协议栈,极大减轻软中断压力。
- TSO/GSO (TCP/Generic Segmentation Offload):分片卸载,发送大包时由网卡负责切片。
开启命令:
ethtool -K eth0 gro on
ethtool -K eth0 gso on
ethtool -K eth0 tso on
四、 总结:排查与调优的标准路径
面对 softirq 飙高,切忌盲目修改内核参数。推荐的落地步骤如下:
- 观察现象:通过
mpstat -P ALL 1查看是否是单核被si占满。 - 定位根因:检查
/proc/softirqs。- 如果是
NET_RX不均匀,说明是中断亲和性或多队列未配置问题。 - 如果整体都高,说明是纯物理流量过载或小包过多。
- 如果是
- 分流实施:优先使用网卡多队列 + 物理中断绑定;若硬件受限,开启 RPS/RFS。
- 提升极限:调整 Ring Buffer、开启 GRO/TSO 硬件卸载,最后微调内核的 budget 和 backlog 参数。
- 极客手段:对于超高 PPS 的 DDoS 攻击或特定业务,考虑引入 XDP (eBPF) 或 DPDK,在软中断之前甚至直接绕过内核协议栈完成包处理。