干过几年Linux服务器运维和性能调优的人,迟早都会碰上这么一个场景:业务高峰期,CPU单核被打满,其他核空闲,网卡收包延迟动不动飙到几毫秒甚至几十毫秒。你查了负载、看了TOP、翻遍了应用日志,最后才发现瓶颈根本不在业务代码里,而是所有网卡中断都挤在了同一个CPU核心上,把那个核活活压死了。这篇文章就围绕“网卡中断绑核”这个优化手段,把原理、操作、验证、进阶玩法一次性讲透,属于直接把工作台搬到你面前的那种分享。
网卡中断绑核,简单说就是把网卡收发包触发的中断请求(IRQ)定向分配到指定的CPU核心上处理,避免中断全部堆积在单个核上形成热点。它解决的核心问题是:高并发网络场景下,CPU 0(通常是默认处理中断的核)负载过高,而其它核心空闲,导致整体吞吐量上不去、延迟不稳定。适合所有使用Linux物理机或虚拟机跑高流量服务的同学参考——尤其是网关、负载均衡、缓存、数据库、WEB服务这类网络密集型应用。
这类优化的价值很容易被低估,但我在实际项目中多次靠它把服务端吞吐量拉高了30%以上,而且不需要改一行业务代码。下面把整套思路和实操细节完整拆开,包括命令、参数、脚本和我踩过的坑。
1. 网卡中断绑核到底解决什么问题
1.1 一次性能排查把我逼到了中断优化这条路上
先讲一个真实案例。某次压测一个网关服务,16核的机器,流量大概到了80万PPS(包每秒)的时候,CPU使用率看起来并不高,才30%左右,但延迟曲线已经开始剧烈抖动。打开top后按1看每个核心的使用率,问题立刻就暴露了:CPU0的使用率接近100%,而其余15个核都在10%以下。
为什么会出现这种极端不均衡?因为默认情况下,大多数网卡驱动程序会把所有中断请求都发送到CPU0——这个设计在单核时代合情合理,但在多核服务器上就成了明显的瓶颈。更让人头疼的是,CPU0除了处理网卡中断,还要承担系统时钟中断、调度器的一部分工作,以及RCU(Read-Copy Update)回调等杂七杂八的内核任务。多股流量叠加在一起,CPU0直接就顶不住了,中断处理延迟急剧上升,网络吞吐量同步下降。
1.2 中断处理的完整链路:从网卡到CPU
要理解绑核的意义,得先看清楚一个网络数据包从网卡到达应用层的完整路径。物理网卡收到数据帧后,通过DMA(直接内存访问)把数据写入内存中的环形缓冲区(Ring Buffer),完成写入后,网卡通过硬件引脚向CPU触发一个中断请求。这时CPU暂停手头的工作,跳转到内核注册的中断处理函数(ISR,Interrupt Service Routine),完成收包、校验、协议栈处理等后续工作。
整个过程涉及两类中断:硬中断(Hard IRQ)和软中断(SoftIRQ)。硬中断处理由网卡驱动程序注册的处理函数执行,处理完就快速返回;而真正消耗大量CPU时间的协议栈处理(IP、TCP/UDP)和流量分发,是在软中断上下文中完成的。硬中断触发后,内核会标记对应的软中断,等硬中断上下文退出后,在当前CPU上继续执行软中断处理。
这个设计的关键点在于:软中断始终在触发它的那个CPU上运行。也就是说,你把硬中断绑定到CPU3,那么后续的软中断处理也会在CPU3上执行。所以绑核一次操作,实际上同时约束了硬中断和软中断的运行位置,这正是它能显著改善CPU负载均衡的原因。理解了这条链路,后面所有的排查和优化就都有方向了。
2. 动手前的准备工作:查清楚你的中断现状
2.1 核心工具:/proc/interrupts 解读
绑核不是拍脑袋随便绑,第一步一定是搞清楚当前系统的中断分布情况。Linux把所有的中断信息都暴露在/proc/interrupts这个虚拟文件里,用cat就能直接查看:
cat /proc/interrupts输出大概是这样的:
CPU0 CPU1 CPU2 CPU3 0: 28 0 0 0 IO-APIC 2-edge timer 8: 1 0 0 0 IO-APIC 1-edge rtc0 24: 9765231 0 0 0 PCI-MSI 524288-edge enp3s0每一行代表一个中断源。第一列是中断号(IRQ Number),中间几列是各CPU上该中断发生的次数,最后一列是中断的设备名称。像enp3s0就是网卡对应的中断,PCI-MSI说明这是通过MSI(Message Signaled Interrupts)方式传递的中断。
判断中断是否扎堆,只需要看对应行在各CPU之间的计数是否均衡。如果一行里只有CPU0的计数在不断增长,其他核心基本不动,那就说明当前中断全压在CPU0上。用下面这条命令可以动态观察,每秒刷新一次:
watch -n1 cat /proc/interrupts重点盯住你要优化那块网卡对应的中断行,看它的计数增长是否集中在某一个或某几个CPU上。另外需要说明的是,现在主流服务器网卡都支持MSI-X,它允许一个网卡申请多个中断号,每个队列对应一个中断号。如果你在输出里看到多个PCI-MSI行都指向同一块网卡,说明网卡的多队列功能已经启用,这个对后面的优化非常有利。
2.2 确认网卡是否支持多队列
多队列(Multi-Queue)是网卡高性能收包的基础能力。传统单队列网卡无论流量多大,只有一个中断号和一个Ring Buffer,所有包都在一条流水线上处理;而支持多队列的网卡(如Intel的i350/i40e、Mellanox的CX系列)可以把Ring Buffer拆分成多个队列,每个队列独立申请中断号,由驱动把收到的包分散到不同队列里,再由不同CPU核心并行处理。
检查网卡是否支持多队列,可以先看中断列表里是不是有多个同网卡的中断号。更准确的方法是查网卡队列信息:
ls /sys/class/net/enp3s0/queues/如果输出里有rx-0、rx-1、rx-2、rx-3等多个接收队列(还有对应的tx-*发送队列),说明多队列已经启动。查看每个队列实际配置的CPU亲和性:
cat /sys/class/net/enp3s0/queues/rx-0/rps_cpus如果这个文件内容全是0,说明当前没有使用RPS(Receive Packet Steering)软件分发机制,中断完全依赖硬件队列和硬中断绑核来处理。
对于不支持多队列的老网卡,或者驱动没开启多队列的情况,KVM虚拟机里常见的virtio-net也支持多队列,但需要在宿主机和虚拟机两端都做配置。如果确认网卡不支持多队列,后续优化就要依赖RPS/RFS这类软件方案来分发软中断,内容我会在第四章展开讲。
2.3 多队列与单队列下的绑核策略差异
多队列和单队列的网卡,绑核策略完全不同,下面用一个表来对照说明:
| 网卡类型 | 中断特征 | 推荐绑核策略 | 核心目标 |
|---|---|---|---|
| 多队列网卡 | 多个中断号,每个队列一个 | 将不同队列绑定到不同CPU核 | 并行处理,充分利用多核 |
| 单队列网卡 | 仅一个中断号 | 绑定到一个专门处理中断的核 | 避免影响其它核心,配合RPS |
| 多队列+高吞吐 | 队列数=核数或倍数 | 一队列对应一核,合理规划NUMA | 降低跨核访问开销 |
| 虚拟化环境 | 队列数取决于virtio配置 | 绑定到同一个NUMA节点内的核 | 减少跨NUMA访存开销 |
单队列网卡即使你把中断绑到一个核上,那个核仍然可能成为瓶颈。所以在规划绑核方案前,先搞清楚网卡类型,不要盲目照搬网上的配置命令。我见过不少案例,网卡明明支持多队列,但驱动用了老的参数导致只起来一个队列,性能翻倍的空间白白浪费了。
3. 网卡中断绑核的标准操作流程
3.1 确定中断号和目标CPU
绑核的第一步是找到目标网卡对应的中断号。前面已经看过/proc/interrupts,现在我们用更精确的方式定位。假设网卡名是enp3s0,先用ethtool确认网卡信息和队列数:
ethtool -l enp3s0输出里会列出一个Combined字段,显示当前网卡实际启用的收发队列数量。之后,通过中断列表找到所有enp3s0相关的行。如果网卡有8个队列,通常会有8个对应的中断号,可能分布在连续的编号区间里。也可以用脚本直接把网卡对应的中断号整理出来:
grep -i enp3s0 /proc/interrupts | awk '{print $1}' | tr -d ':'拿到中断号之后,下一步就是确定你要把这些中断绑定到哪些CPU核心。这里有一个最基本的规则:优先绑定到同一NUMA节点内的核心上。查看CPU拓扑:
lscpu重点关注NUMA node0 CPU(s)和NUMA node1 CPU(s)这两行的信息。如果网卡挂在node0的PCIe总线上,那么中断绑定到node0的核上,CPU访问网卡DMA映射的内存就在本地内存上,延迟最低;如果你绑到node1的核上,每次中断处理都要跨NUMA访问内存,性能反而不如不绑。
3.2 通过smp_affinity设置中断亲和性
Linux内核为每个中断号都提供了两个亲和性控制文件:/proc/irq/<中断号>/smp_affinity(用十六进制掩码表示CPU列表)和/proc/irq/<中断号>/smp_affinity_list(用十进制列表表示具体CPU编号)。smp_affinity_list更直观,操作起来也更容易理解:
# 将中断号128绑定到CPU0和CPU1 echo 0-1 > /proc/irq/128/smp_affinity_list比如echo 0-1表示允许CPU0和CPU1处理该中断,实际运行时会根据负载在两者之间分配。如果只想绑定到单个CPU:
echo 3 > /proc/irq/128/smp_affinity_list如果使用smp_affinity,需要把CPU编号换算成十六进制位掩码。比如想让CPU3处理中断,就把第3位置1,得到二进制1000,十六进制就是8:
echo 8 > /proc/irq/128/smp_affinity这里特别容易踩坑的是十六进制掩码的计算。有个快速方法:先用printf把十进制CPU编号转换成十六进制掩码。比如要绑定CPU5和CPU7,CPU列表换算成掩码是(1<<5) | (1<<7) = 32+128 = 160,十六进制就是a0。初学者建议直接用smp_affinity_list,省去换算的麻烦。
设置完成后,再次查看/proc/interrupts,中断会在新绑定的CPU上递增计数。但注意,老的计数不会清零,你需要观察新增的部分是否落在目标CPU上,才能确认绑定生效。
3.3 使用irqbalance自动管理还是手动绑核
很多Linux发行版默认装了irqbalance服务,它会周期性扫描系统中断分布情况,并根据CPU负载自动调整中断亲和性。这个服务在普通桌面系统和小型服务器上问题不大,但对于追求极致性能的生产环境,我建议直接关掉它,因为它会打乱你手动设置的任何绑核方案。
检查并关闭irqbalance:
# 查看状态 systemctl status irqbalance # 停止并禁用 systemctl stop irqbalance systemctl disable irqbalance为什么手动绑定更可靠?irqbalance只考虑CPU使用率来决定中断迁移策略,它不知道你的业务是网络密集型还是计算密集型,也不知道哪些网卡是关键路径。它默认的目标是让所有CPU负载相对均匀,但这恰恰和高性能网络的优化目标冲突——网络密集型应用需要把中断集中到指定的几个核上,让业务线程独占其余核心,而不是大家一起分摊。
关闭irqbalance后,手动设置的smp_affinity才能稳定生效。别忘了检查系统里是否还有其它自动调整中断的机制,比如tuned服务如果启用了某些性能调优profile,也可能在后台改中断亲和性,需要一并留意。
3.4 绑定后的验证与效果评估
完成绑核后,必须用数据说话,确认优化真正产生了效果。第一步看中断是否已经均匀分布到目标CPU,用top按1键,观察各核心的使用率是否趋于均衡;或者借用一个采样脚本,对比绑定前后各CPU的中断次数增量。
更关键的验证指标是网络延迟和吞吐量的变化。我常用的工具是perf和mpstat:
# 查看各CPU的中断和软中断分布 mpstat -I CPU -P ALL 1输出里的intr/s列代表每秒中断次数,各CPU之间应该看到明显差异,且分布在你预期的核上。同时用perf监测软中断处理是否均匀:
perf stat -e irq_vectors:local_timer_entry,softirq:softirq_entry -a sleep 5除了观察系统侧的数据,业务侧也要同步对比:用压测工具(如wrk、iperf3、dpdk-testpmd等)跑流量,对比绑核前后服务的P99延迟和吞吐量。我常见的效果是:绑核后吞吐量提升20%~50%,延迟抖动大幅减少,CPU0从打满状态降到30%以下。如果这些指标没有明显改善,甚至变差了,大概率是绑定策略有问题,比如跨NUMA绑定或者软中断和业务线程挤在同一个核上,后面第五章会讲如何排查。
4. 进阶:结合网卡多队列、RPS/RSS与numactl的完整优化方案
4.1 RSS(Receive Side Scaling)与网卡多队列的配合
RSS是网卡硬件层面的多队列负载均衡机制。启用RSS后,网卡会根据数据包的五元组信息(源IP、目的IP、源端口、目的端口、协议)计算哈希值,把哈希结果映射到不同的接收队列。这样同一个TCP连接的所有包都会被分到同一个队列,避免乱序;不同连接则被分散到不同队列,实现并行处理。
查看当前网卡的RSS配置:
ethtool -x enp3s0输出会显示当前网卡的RSS哈希字段和重定向表(Indirection Table)。如果哈希字段只有src-ip和dst-ip,建议开启四元组/五元组哈希,让不同TCP连接更容易被分散到不同队列:
ethtool -N enp3s0 rx-flow-hash tcp4 sdfnsdfn分别代表源IP、目的IP、源端口、目的端口。对于UDP,用udp4同理:
ethtool -N enp3s0 rx-flow-hash udp4 sdfn设置完后,检查重定向表:
ethtool -x enp3s0这时每个队列对应的CPU编号会直观地列出来。通过把网卡队列数设为与CPU核数一致(或适当倍数),再配合中断绑核,可以让每个CPU核处理一个硬件队列的中断,实现真正的并行收包。
4.2 RPS/RFS 在软件层面分发中断
对于单队列网卡或者在虚拟化环境中无法启用硬件多队列的场景,Linux内核提供的RPS(Receive Packet Steering)机制可以在软件层面把收到的包分发到不同CPU核心的软中断队列中。开启RPS的关键就是修改队列目录下的rps_cpus文件。
假设你要让rx-0队列的包分发到CPU2到CPU7这6个核上,先把CPU掩码算出来。CPU2到CPU7对应位掩码为11111100(二进制从高位到低位分别是CPU7到CPU0,实际换算按位从CPU0开始),十进制为252,十六进制为fc:
echo fc > /sys/class/net/enp3s0/queues/rx-0/rps_cpus更直观的写法是直接用十六进制掩码。不太会算的同学,可以借助这个思路:把CPU编号列表折算成二进制位,从CPU0开始从右往左排,比如CPU0、CPU1、CPU2对应00000011,也就是十六进制03;如果你的环境是40核起步的服务器,记得掩码要按64位考虑,写成fc000000000000之类的形式。
RFS(Receive Flow Steering)是RPS的增强版,它能根据应用的运行CPU位置,把同一个流的包发送到正在处理该流的CPU上,从而提升CPU缓存命中率。开启RFS需要设置/proc/sys/net/core/rps_sock_flow_entries和每个队列的rps_flow_cnt:
# 假设可能同时有32768个活动流 echo 32768 > /proc/sys/net/core/rps_sock_flow_entries echo 4096 > /sys/class/net/enp3s0/queues/rx-0/rps_flow_cntRPS/RFS不是银弹,它需要消耗额外的CPU来做分发计算,如果本来CPU已经吃紧,效果会打折扣。但在单队列网卡上,这是唯一无需改硬件就能实现中断负载均衡的办法。
4.3 结合numactl进行CPU核心的合理规划
绑核优化到了一定程度,真正的瓶颈往往不再是中断处理本身,而是CPU访问内存的路径。NUMA架构下,每个CPU核访问本地内存和远端内存在延迟上能差出1.5到2倍。所以绑核方案要配合NUMA规划一起来做。
完整规划思路是分层的:先确认网卡挂载在哪个NUMA节点上,查看PCIe设备对应的NUMA节点编号:
cat /sys/bus/pci/devices/0000:03:00.0/numa_node03:00.0是网卡的PCI地址,可以根据lspci输出找到。假设结果显示网卡在node0,那么:
- 中断绑核优先使用node0的CPU核,比如node0有CPU0~7,就把网卡的多个中断分别绑到0~7。
- 网卡中断绑在node0的核上,后续运行DPDK或专用收包线程时,用
numactl --cpunodebind=0 --membind=0启动进程,保证内存分配也在node0上。 - 业务线程则绑到node1的核上,避免和中断处理抢CPU。
命令示例:
# 启动一个绑定到node1的CPU8~15上的业务进程,内存也优先从node1分配 numactl --cpunodebind=1 --membind=1 ./your_service这套组合拳打下来,中断处理、内存访问、业务计算各占一块地盘,互不干扰,性能和稳定性都能上一个台阶。
4.4 完整优化脚本参考
我把自己用惯的一套优化脚本简化了一下,放到下面。生产环境使用前请根据自己的网卡名、CPU拓扑、中断号进行修改,建议先在测试环境完整验证一遍。
#!/bin/bash # 网卡中断绑核与RPS优化脚本 # 请根据实际环境修改变量 # ============ 配置区 ============ IFACE=enp3s0 # 用于中断绑定的CPU列表(根据NUMA拓扑调整) IRQ_CPUS="0 1 2 3" # 用于RPS分发的CPU掩码(十六进制,按需计算) RPS_CPUS_MASK="0f" # 期望开启的硬件队列数 QUEUE_COUNT=4 # ================================ # 1. 停止irqbalance systemctl stop irqbalance 2>/dev/null systemctl disable irqbalance 2>/dev/null # 2. 设置网卡队列数(如果驱动支持) ethtool -L $IFACE combined $QUEUE_COUNT 2>/dev/null # 3. 获取网卡对应的所有中断号 IRQS=$(grep -i $IFACE /proc/interrupts | awk -F: '{print $1}' | tr -d ' ') # 4. 将中断分别绑定到指定CPU i=0 for irq in $IRQS; do cpu=$(echo $IRQ_CPUS | tr ' ' '\n' | sed -n "$((i % $(echo $IRQ_CPUS | wc -w) + 1))p") echo $cpu > /proc/irq/$irq/smp_affinity_list i=$((i + 1)) done # 5. 对每个RX队列开启RPS for rxq in /sys/class/net/$IFACE/queues/rx-*; do echo $RPS_CPUS_MASK > $rxq/rps_cpus done # 6. 打印当前中断分布确认 echo "=== 当前中断分布 ===" grep -i $IFACE /proc/interrupts脚本的关键点在于:把多个中断号轮询分配到指定的CPU列表上,确保不出现两个队列的中断扎堆在同一核上的情况。如果你有16个中断号、4个目标核,轮询会让每4个中断对应一个核,充分利用有限的核资源。
5. 常见问题与排查技巧实录
5.1 中断号不固定/重启失效怎么办
手动改动/proc/irq/下的配置只能临时生效,服务器一重启就全部还原。要让绑核配置永久化,有几种方案。
最直接的办法是把脚本写进systemd服务,开机自动执行。创建一个服务文件/etc/systemd/system/irq-affinity.service:
[Unit] Description=Set IRQ CPU Affinity After=network.target [Service] Type=oneshot ExecStart=/usr/local/sbin/irq-affinity.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target然后执行:
chmod +x /usr/local/sbin/irq-affinity.sh systemctl daemon-reload systemctl enable irq-affinity.service另一种思路是用irqbalance的配置文件,保留服务并让它只管理你指定的中断。但如前所述,生产环境我建议彻底关掉irqbalance,手动脚本更可控。
需要注意的是,有些系统有irqaffinity内核启动参数,可以在GRUB配置里指定默认中断亲和性。例如编辑/etc/default/grub,在GRUB_CMDLINE_LINUX中加入:
irqaffinity=0-3然后运行update-grub并重启。这个方式对所有中断统一生效,但对不同网卡需要不同绑定策略的场景来说不够灵活,我一般只在紧急兜底时使用。
5.2 绑核后反而性能下降?排查思路
有时做完绑核,吞吐量不升反降,延迟还变高了。这种情况通常不是绑核本身错了,而是方案和当前的业务模型不匹配。按下面三个方向逐一排查。
第一,检查是否发生了跨NUMA访问。如果你的网卡在node0,却把中断绑到了node1的核上,每次DMA数据拷贝和协议栈访问都会产生远端内存访问,性能自然劣化。用numastat查看内存分配是否跨节点严重失配。
第二,确认软中断处理核是否和业务线程的核冲突。如果中断绑在CPU2~3,而你的应用主线程也恰好跑在CPU2~3上,两边抢CPU资源,延迟照样飙高。中断绑核的核心思想是“让专门的核处理中断”,所以要保证业务线程分散到其它核上,必要时用taskset -c给业务线程指定CPU。
第三,确认是否有RPS配置叠加导致重复分发。如果网卡本身支持多队列且已开启RSS,同时又设置了RPS,两层分发机制可能互相干扰,增加额外的计算开销。通常硬件多队列启用时,不需要再开RPS,二者择一即可。
5.3 如何确认软中断(softirq)是否均匀分布在目标CPU上
只盯着/proc/interrupts看硬中断是不够的,还要确认软中断确实在预期核上执行。查看各CPU的软中断统计:
cat /proc/softirqs输出中的NET_RX和NET_TX两行是网卡收发包软中断的关键,分别观察各CPU的计数增长情况。正常情况下,增长应集中在你绑定的目标核上。
如果发现NET_RX计数增长在未绑定的核上,原因通常是网络数据没有走硬件多队列,而是通过RPS分发到了其它CPU。这时需要调整rps_cpus配置,或者检查是否配置了flow steering相关的规则。
更细的观察可以用perf top,在内核层面看哪个函数在哪个CPU上消耗资源:
perf top -C 2,3这里的-C参数指定查看CPU2和CPU3上正在执行的热点函数。如果看到process_backlog、net_rx_action这类函数在目标核上占比很高,说明收包软中断确实在按预期方式运行。
5.4 实战排查命令速查表
把整个排查过程中最常用的命令整理成一张表,方便直接复制使用:
| 目标 | 命令 | 说明 |
|---|---|---|
| 查看中断分布 | cat /proc/interrupts | 看各CPU中断计数 |
| 查看软中断分布 | cat /proc/softirqs | 看NET_RX/NET_TX分布 |
| 查看网卡队列数 | ethtool -l enp3s0 | 确认Combined队列数 |
| 查看RSS配置 | ethtool -x enp3s0 | 看哈希字段和重定向表 |
| 修改RSS哈希 | ethtool -N enp3s0 rx-flow-hash tcp4 sdfn | 开启四元组哈希 |
| 修改中断亲和性 | echo N > /proc/irq/<irq>/smp_affinity_list | 直接指定CPU编号 |
| 查看NUMA拓扑 | lscpu | 看各NUMA节点包含的CPU |
| 查看设备NUMA节点 | cat /sys/bus/pci/devices/0000:03:00.0/numa_node | 确认网卡所在节点 |
| 监控各CPU中断 | mpstat -I CPU -P ALL 1 | 每秒刷新 |
| 设置RPS掩码 | echo 0f > /sys/class/net/enp3s0/queues/rx-0/rps_cpus | 软件分发软中断 |
| 查看软中断热点 | perf top -C 2,3 | 分析指定核上的内核函数 |
这套命令组合足以覆盖从问题发现、原因定位、方案实施到效果验证的完整链路。实际生产环境里,我通常先用cat /proc/interrupts扫一遍,再用mpstat盯几秒钟,基本就能判断出该不该做绑核优化。
5.5 绑核优化里最容易被忽略的CPU核隔离问题
最后补充一个很多人都会忽略的细节:绑核优化要配合CPU隔离才能真正发挥全部性能。Linux内核本身的调度器、内核线程、定时器等仍然可能在你绑定的核心上运行,干扰中断处理和软中断执行的实时性。
把一部分CPU从通用调度器中隔离出来,可以使用内核启动参数isolcpus。例如把CPU4~7隔离:
isolcpus=4,5,6,7 nohz_full=4,5,6,7 rcu_nocbs=4,5,6,7nohz_full可以减少隔离核上的时钟中断频率,rcu_nocbs则让RCU回调不在这些核上执行。配合irq affinity,这些隔离核就能近乎专一地处理网卡中断,性能表现更稳定。
隔离CPU是双刃剑:隔离后普通用户态进程默认无法调度到这些核上,如果忘记配置合适的亲和性,业务线程会少掉一大部分计算资源。规划时要全局考虑中断处理、业务线程、内核进程三方的CPU资源分配,不能顾此失彼。
我在实际项目里常用的划分方式是:16核的机器,CPU0保留给系统内核和其它杂项任务,CPU1~7给业务线程,CPU8~15做中断绑定(前提是网卡挂在对应NUMA节点上,并且网卡有足够的队列数)。如果网卡只有4个队列,就只绑定其中4个核,剩下的作为软中断处理的余量。这种“隔离+绑核”的组合方案,在高负载网关场景下实测可以把P99延迟降低50%以上,效果非常明显。
另外,在做完绑核和CPU隔离之后,重启服务之前一定要检查一下systemd的CPUAffinity设置,因为systemd可能默认把服务限制在特定CPU列表,跟你的绑核方案冲突。在服务文件里显式设置CPUAffinity=和NUMAMask=,能避免很多“明明绑了却没生效”的怪问题。
这套网卡中断绑核的优化方法,我先后在多家公司的生产环境里实践过,从几百台规模到几千台规模都验证过效果。操作本身不复杂,核心是理解背后的中断处理链路,并且每一步都要用数据验证。如果你正在被CPU0打满、网络延迟抖动、吞吐量上不去这些问题困扰,按照文章里的步骤试一遍,大概率能找到一个之前被忽略的优化空间。