HOOOS

高并发下 Linux 服务器 softirq 飙高?从底层原理到实战调优指南

0 5 内核性能观测者 Linux性能调优网络协议栈
Apple

在高并发(尤其是海量小包网络吞吐)的场景下,Linux 服务器的 CPU 使用率中经常会出现 si (softirq,软中断) 占比极高、甚至单核被压死的现象。伴随而来的往往是丢包、延迟飙升以及吞吐量严重下滑。

要彻底解决这个问题,需要从 Linux 内核的网卡收包机制说起,并结合系统级观测工具进行针对性调优。


一、 为什么高并发会导致 softirq 飙高?

在 Linux 的网络接收路径中,网卡与内核的交互经历了两阶段:

[ 网卡收到数据包 ] 
       │
       ▼
 1. 触发硬件中断 (Hard IRQ) ──► 极其短暂,只做最简单的事:清空网卡寄存器,向内核发起 Poll 请求
       │
       ▼
 2. 触发软中断 (Soft IRQ)   ──► 耗时较长:执行 NAPI 轮询,将数据包从 Ring Buffer 拷贝并封装成 sk_buff,送入协议栈

在网络并发极高(如每秒数十万甚至数百万 PPS)时,软中断(尤其是 NET_RX)会面临极大的压力:

  1. 单核处理瓶颈(中断亲和性不均):默认情况下,网卡的硬件中断往往会全部打到某一个 CPU 核心上(通常是 CPU0)。这就导致该核心不断触发 NET_RX 软中断。一旦单核被软中断占满(si 接近 100%),这个核心就无法处理正常的进程上下文,导致包处理延迟增加,网卡 Ring Buffer 溢出并丢包。
  2. ksoftirqd 线程过载:当软中断处理函数的执行时间过长,或者一次性处理的包太多时,为了避免单次中断处理完全霸占 CPU,内核会将后续的软中断处理工作移交给内核线程 ksoftirqd/X(X 为 CPU 编号)。如果 ksoftirqd 频繁运行,说明软中断已经严重过载。
  3. 小包开销巨大:对于 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_errorsrx_missed_errorsrx_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 飙高,切忌盲目修改内核参数。推荐的落地步骤如下:

  1. 观察现象:通过 mpstat -P ALL 1 查看是否是单核si 占满。
  2. 定位根因:检查 /proc/softirqs
    • 如果是 NET_RX 不均匀,说明是中断亲和性多队列未配置问题。
    • 如果整体都高,说明是纯物理流量过载小包过多
  3. 分流实施:优先使用网卡多队列 + 物理中断绑定;若硬件受限,开启 RPS/RFS
  4. 提升极限:调整 Ring Buffer、开启 GRO/TSO 硬件卸载,最后微调内核的 budgetbacklog 参数。
  5. 极客手段:对于超高 PPS 的 DDoS 攻击或特定业务,考虑引入 XDP (eBPF)DPDK,在软中断之前甚至直接绕过内核协议栈完成包处理。

点评评价

captcha
健康