HOOOS

生产环境网卡丢包、CPU单核软中断100%?不重启服务器动态调整网卡队列与中断绑定的硬核指南

0 6 运维架构说 Linux网卡调优系统性能优化
Apple

在承载高并发、高吞吐量业务的 Linux 服务器上,网络性能瓶颈经常表现为:某个 CPU 核心的软中断(softirq,si 指标)飙升至 100%,而其他核心却在“围观”。伴随而来的,是网卡频繁丢包(packet drops)和业务响应延迟抖动。

这种现象通常是因为网卡多队列(Multi-Queue)未启用,或者网卡中断(IRQ)全部绑定在了默认的 CPU 0 上,导致单核处理上限成为整机瓶颈。

在不重启生产服务器、不中断业务的前提下,如何动态、安全、精准地调整网卡队列和中断绑定?本文将为你梳理一套可直接用于生产环境的操作规程。


核心调优思路

进行操作前,必须遵循以下性能和安全原则:

  1. NUMA 亲和性(NUMA Affinity):网卡中断必须绑定在网卡物理插槽所在的同一个 NUMA 节点的 CPU 核心上。跨 NUMA 节点投递中断会导致严重的 QPI/UPI 跨片总线延迟。
  2. 避开 CPU 0:CPU 0 通常承载了大量系统级定时器和底层中断,尽量将网卡中断绑定到其他空闲核心。
  3. 安全防线:关闭 irqbalance:默认的 irqbalance 服务会动态重写中断绑定关系,必须在手动绑定前对其进行配置或关闭,否则手动配置会被瞬间覆盖。

第一步:定位瓶颈与环境勘测

在动手前,先摸清当前系统和网卡的硬件现状。

1. 确认当前中断分布

使用以下命令观察当前网卡中断在各个 CPU 核心上的分布:

watch -d -n 1 "cat /proc/interrupts | grep -E 'eth0|em1|enp'"

(注意:请将 eth0 等替换为你实际的网卡名称)

如果你发现某几个特定的中断号(IRQ)后面的计数值只在某一两个 CPU 列上疯狂累加,而其他 CPU 列全是 0,说明存在严重的中断不均衡。

2. 检查网卡当前队列与最大限制

使用 ethtool 检查网卡硬件支持的队列数,以及当前启用了多少队列:

ethtool -l eth0

输出示例:

Channel parameters for eth0:
Pre-set maximums:
RX:             0
TX:             0
Other:          0
Combined:       64  # 硬件支持的最大队列数
Current hardware settings:
RX:             0
TX:             0
Other:          0
Combined:       8   # 当前实际启用的队列数

如果 Current Combined 远小于 Pre-set maximums,且小于系统的 CPU 核心数,说明网卡队列还没有充分利用。

3. 获取网卡所在的 NUMA 节点及对应的 CPU 核心

找出网卡挂载在哪个物理 CPU 节点上:

cat /sys/class/net/eth0/device/numa_node

假设返回值为 0。接着查看 NUMA 节点 0 包含哪些 CPU 核心:

lscpu | grep "NUMA node0"

输出示例:

NUMA node0 CPU(s):     0-15,32-47

这表明,后续我们在做中断绑定时,最佳的绑定范围是 1-15 核心(避开 0 核心,且在 NUMA 0 节点内)。


第二步:停用或配置 irqbalance 守护进程

irqbalance 会在后台不断调整中断亲和性。如果不做处理,你手动的绑定很快就会失效。

方案 A:直接停止服务(推荐,最安全)

如果你的服务器是专门跑高负载业务的(如 Nginx、Redis、Database),建议直接关闭并禁用该服务,全面采用手动精细化绑定:

systemctl stop irqbalance
systemctl disable irqbalance

方案 B:配置 irqbalance 排除目标网卡

如果你不希望全局关闭 irqbalance,可以修改其配置文件(CentOS 在 /etc/sysconfig/irqbalance,Ubuntu 在 /etc/default/irqbalance),将目标网卡隔离:

# 找到/添加以下配置,排除 eth0
IRQBALANCE_ARGS="--banirq=xxx --banirq=yyy" 
# 或者通过网卡名排除(取决于发行版支持)

第三步:动态安全调整网卡队列数

1. 安全评估

调整网卡队列会触发网卡驱动的重新加载(类似于微秒级的 Reset),这会导致短暂的瞬时丢包。

  • 安全操作建议:请确保在业务低谷期,或在上层负载均衡(SLB/Nginx)中已将该节点的流量切除后再行操作。

2. 动态调整命令

eth0 的队列数调整为与物理核心数一致(或对齐 NUMA 节点核心数,通常建议不超过 16 或者是 2 的幂次方):

ethtool -L eth0 combined 16

调整后,再次执行 ethtool -l eth0 确认是否生效。此时,网卡会生成 16 个接收/发送通道,并在 /proc/interrupts 中注册 16 个新的中断号。


第四步:计算与执行中断绑定(IRQ Affinity)

网卡多队列启用后,我们需要把这 16 个队列的中断信号,均匀地分配到物理 CPU 的不同核心上。

1. 理解 CPU 亲和性掩码(Affinity Mask)

Linux 通过十六进制掩码控制中断绑定。每一位二进制代表一个 CPU 核心(从右往左依次是 CPU 0, CPU 1, CPU 2...)。

  • 绑定到 CPU 1 (二进制 0010) $\rightarrow$ 十六进制 2
  • 绑定到 CPU 2 (二进制 0100) $\rightarrow$ 十六进制 4
  • 绑定到 CPU 3 (二进制 1000) $\rightarrow$ 十六进制 8
  • 绑定到 CPU 4 (二进制 0001 0000) $\rightarrow$ 十六进制 10
  • 绑定到 CPU 1,2 (二进制 0110) $\rightarrow$ 十六进制 6

2. 找出网卡对应的中断号

利用下面命令过滤出该网卡当前所有的中断 ID:

cat /proc/interrupts | grep -E "eth0|enp.*-TxRx" | awk -F: '{print $1}' | tr -d ' '

输出可能类似于一串数字:

121
122
123
...
136

3. 手动绑定演示

假设我们要把中断号 121 绑定到 CPU 1,中断 122 绑定到 CPU 2:

echo "2" > /proc/irq/121/smp_affinity   # 2 的二进制是 0010,代表 CPU 1
echo "4" > /proc/irq/122/smp_affinity   # 4 的二进制是 0100,代表 CPU 2
echo "8" > /proc/irq/123/smp_affinity   # 8 的二进制是 1000,代表 CPU 3

4. 自动化绑定脚本(生产实用)

面对数十个网卡队列,手动计算掩码极易出错。下面提供一个开箱即用的 Bash 脚本,自动获取网卡中断号并一对一绑定到 NUMA 0 对应的 CPU 核心上(避开 CPU 0,从 CPU 1 开始顺序绑定):

#!/bin/bash
# 目标网卡
NIC="eth0"

# 1. 确保 irqbalance 已关闭
systemctl stop irqbalance

# 2. 获取该网卡的所有中断号
IRQS=$(cat /proc/interrupts | grep -E "${NIC}-.*" | awk -F: '{print $1}' | tr -d ' ')

if [ -z "$IRQS" ]; then
    # 兼容某些驱动命名格式不带横杠的情况
    IRQS=$(cat /proc/interrupts | grep -E "${NIC}" | awk -F: '{print $1}' | tr -d ' ')
fi

# 3. 开始绑定,从 CPU 1 开始(避开 CPU 0)
CORE=1
for IRQ in $IRQS; do
    # 计算当前核心对应的十六进制掩码
    MASK=$(printf "%x" $((1 << CORE)))
    
    echo "Binding IRQ $IRQ to CPU $CORE (Mask: $MASK)"
    echo "$MASK" > /proc/irq/$IRQ/smp_affinity
    
    # 递增核心,如果超过你希望绑定的最大核心数(例如 15),则循环回核心 1
    CORE=$((CORE + 1))
    if [ $CORE -gt 15 ]; then
        CORE=1
    fi
done

将上述代码保存为 bind_irq.sh,赋予执行权限 chmod +x bind_irq.sh 并用 sudo 运行。


第五步:终极兜底方案 —— 启用 RPS/RFS

如果你的网卡非常老旧(或者在虚拟机、云主机实例里),硬件本身不支持多队列(只有 1 个 Combined 队列),但你依然面临单核软中断 100% 的问题,该怎么办?

这时需要开启 Linux 内核软件模拟的多队列方案:RPS(Receive Packet Steering)RFS(Receive Flow Steering)

1. 启用 RPS

RPS 可以将单个网卡队列接收到的报文,在软件层分发到多个 CPU 的 backlog 队列中进行软中断处理。

eth0 的第 0 个接收队列,把流量分发给 CPU 1-15(掩码为 fffe):

echo "fffe" > /sys/class/net/eth0/queues/rx-0/rps_cpus

2. 启用 RFS

RFS 在 RPS 基础上更进一步,它会考虑应用程序所在的 CPU 核心,尽量把报文投递到运行该应用的 CPU 上,提升 CPU 缓存命中率。

设置全局流表最大哈希表项数(推荐设为 32768):

sysctl -w net.core.rps_sock_flow_entries=32768

设置网卡接收队列的流表数(通常设为 rps_sock_flow_entries / 队列数):

echo "2048" > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

验证与监控

完成配置后,使用 mpstat 工具观察各个 CPU 的软中断占比:

mpstat -P ALL 1

观察 %soft 列,正常的调优结果应当是:之前某单核 100% 的软中断已经消失,软中断被均匀、平摊地分配到了你指定的 1-15 号 CPU 核心上,网络吞吐量上升,丢包计数归零。

同时,可以通过以下命令观察网卡底层是否还有丢包:

ethtool -S eth0 | grep -E "drop|fifo|miss"

只要上述计数器不再增长,就说明你的网卡队列与中断绑定调整成功,系统已平稳度过高流量瓶颈期。

点评评价

captcha
健康