在承载高并发、高吞吐量业务的 Linux 服务器上,网络性能瓶颈经常表现为:某个 CPU 核心的软中断(softirq,si 指标)飙升至 100%,而其他核心却在“围观”。伴随而来的,是网卡频繁丢包(packet drops)和业务响应延迟抖动。
这种现象通常是因为网卡多队列(Multi-Queue)未启用,或者网卡中断(IRQ)全部绑定在了默认的 CPU 0 上,导致单核处理上限成为整机瓶颈。
在不重启生产服务器、不中断业务的前提下,如何动态、安全、精准地调整网卡队列和中断绑定?本文将为你梳理一套可直接用于生产环境的操作规程。
核心调优思路
进行操作前,必须遵循以下性能和安全原则:
- NUMA 亲和性(NUMA Affinity):网卡中断必须绑定在网卡物理插槽所在的同一个 NUMA 节点的 CPU 核心上。跨 NUMA 节点投递中断会导致严重的 QPI/UPI 跨片总线延迟。
- 避开 CPU 0:CPU 0 通常承载了大量系统级定时器和底层中断,尽量将网卡中断绑定到其他空闲核心。
- 安全防线:关闭
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"
只要上述计数器不再增长,就说明你的网卡队列与中断绑定调整成功,系统已平稳度过高流量瓶颈期。