整体 CPU 才 20%,一个核却烧到 100%?中断全堆它头上了
场景:整体 CPU 20%,单核 100% 告警,网卡吞吐从 900MB/s 腰斩到 400MB/s 路径:top 单核 → /proc/interrupts 中断分布 → irqbalance 服务状态 → IRQ 亲和原理 → 修复
上篇我们聊了容器 CFS 节流(throttling)如何偷偷挂线程,这次从"容器"回到"物理机"——一个 16 核的网关机,整体 CPU 才 20%,却有个核常年烧在 100%。应用层日志干干净净,RT 却抖成了心电图。
症状
现象:整体 20%,单核 100%
下午 15:30,监控告警:CPU0 使用率 99.8%,持续 10 分钟。但同一块面板上,整机平均 CPU 只有 20%。
API 网关的 P99 RT 从 8ms 抖到 80ms,网卡吞吐从 900MB/s 掉到 400MB/s——流量没降,出口却像是被人掐了脖子。
监控面板上,整机 CPU 平均 20%,但 CPU0 的使用率曲线像一根烧红的针一样刺在 100%。
第一反应和所有人一样:翻应用日志。没有慢 SQL、没有锁竞争、没有 GC 抖动。top 按 1 展开后真相浮出来——CPU0 的 hi 列占着 95%,其他 15 个核全在围观。
hi 是什么? top 的 hi 列表示 CPU 花在处理硬件中断(硬中断)上的时间占比。网卡收包、磁盘 IO 完成、定时器到期都会触发硬件中断。正常情况 hi 应在 1% 以下,超过 50% 说明硬件中断正在吞噬 CPU。注意:硬中断和软中断在 top 里是两列——hi(硬件中断处理)和 si(软中断处理),这次问题在 hi 上。
整体 CPU 只有 20% 不是因为系统闲——是 16 个核里 1 个在燃烧,15 个在睡觉,平均值把火情盖住了。平均值掩盖了单核瓶颈,这是 CPU 排查里最经典的视觉陷阱。
反直觉:加机器为什么没用?
团队第一反应还是那三板斧——重启、加机器、调 JVM 参数。但这次都有个直觉上的坑:
加机器加的是应用进程的核,不是中断的核。 中断由内核分配,不随应用扩缩容迁移。你加一台机器,中断还是堆在原来的 CPU0 上——新机器的核闲着,旧机器的 CPU0 继续烧。
诊察
命令 1:top -1 确认单核分布

CPU0 的 hi 95%,si 5%;其他核 hi 全在 0-1%。硬中断全部堆在 CPU0——和上次软中断篇(si 堆单核)症状不同,这次是 hi(硬中断)而不是 si(软中断)。
为什么我先跑 top 而不是直接看 /proc/interrupts? top 3 秒给出"哪个核、哪类时间"的全局图,先确定方向。
/proc/interrupts是定位细节的,等确认是中断问题后再下钻。排查原则:先用最快的命令确认方向,再用精确的工具定位。
命令 2:/proc/interrupts 看中断落在哪个核
知道是硬中断了,但是哪个中断、落在哪个核?/proc/interrupts 给出了每个中断在每颗核上的累计计数——看哪个涨得快、落在哪,就是它。

4 个网卡队列中断(eth0-TxRx-0~3)全部堆在 CPU0,计数 1-4 亿,其他 15 核全为 0。10G 网卡满速跑时,1500B 的 MTU 下 900MB/s 约等于每秒 60 万个包,每个包触发一次中断,CPU0 光响应中断就忙不过来。
为什么网卡有 4 个队列还是堆在 CPU0? 网卡多队列(RSS)只是把流量 hash 到 4 个队列,但每个队列的中断可以绑定到任意核。队列的 MSI-X 向量在注册时绑定到 boot CPU(默认 CPU0)——没有 irqbalance 或者它没干活,4 个队列就全部落在一颗核上。
命令 3:smp_affinity + irqbalance 服务状态
中断到底允不允许跑在其他核?看亲和性掩码:

三个证据连起来读:
smp_affinity_list全是0——4 个队列中断只允许在 CPU0 上触发,其他核根本没资格处理;systemctl status显示Active: inactive (dead)——irqbalance 服务根本没在跑;cat /etc/sysconfig/irqbalance发现IRQBALANCE_BANNED_CPUS="fffe"——十六进制掩码,bit 位为 1 表示该 CPU 被禁止参与中断均衡。fffe=1111 1111 1111 1110,bit0 是 0、bit1-15 全是 1:只允许 CPU0 接收中断,其余 15 颗核全被 ban 了。
服务和配置两层都没干活——这是本次排查的"有鬼"时刻:不是硬件坏、不是驱动 bug,是一行配置文件把 15 颗核全锁死了。
路径 — 下次遇到直接跑
三步定位命令组合
看到 hi% > 50% 时,按这个顺序跑:
# 1. 看哪个核在扛(3 秒)
top -1 # 看 hi% + 单核分布
# 2. 看中断落在哪、是哪个中断(5 秒)
grep -E 'eth0|ens|em[0-9]' /proc/interrupts # 看网卡中断计数分布
mpstat -P ALL 1 # 更精确:逐核 %irq/%soft 列
# 3. 看亲和性 + irqbalance 状态(3 秒)
cat /proc/irq/46/smp_affinity_list
systemctl status irqbalance
cat /etc/sysconfig/irqbalance # 或 /etc/default/irqbalance(Debian)
判断规则
| 看到 | 说明 |
|---|---|
hi% 高、si% 低 |
硬件中断问题(不是软中断) |
| 中断计数全堆 CPU0 列 | 中断没做均衡 |
smp_affinity_list 全是 0 |
亲和性被限制或没分散 |
irqbalance inactive 或 BANNED_CPUS 有值 |
均衡服务没跑/配置把核 ban 了 |
定位 — 内核为什么让一个核扛下所有?

这不是应用层的问题——是系统层的中断分配环节出了问题:内核把所有 IRQ 亲和到 CPU0,而 irqbalance 被禁用、BANNED_CPUS 又把其余 15 核锁死。 回看症状段"应用层日志干干净净"的排除结论——应用层确实没病,病在内核的中断路由上。
硬中断默认落在 CPU0
硬件中断的亲和性由 /proc/irq/{N}/smp_affinity 控制——这是一个 bitmask,bit 位为 1 表示该中断允许在这颗核上触发。多数硬件中断在 boot 时默认落在 CPU0(boot CPU)。
为什么默认 CPU0?因为系统初始化时中断控制器(APIC)只把 CPU0 设为可用目标。而本文场景的网卡 MSI-X 向量(Message Signaled Interrupts,消息信号中断,PCIe 设备写特定地址触发中断的方式)在驱动注册时绑定到 boot CPU——如果没有 irqbalance 或驱动主动改写亲和性,中断就永远留在 CPU0。
irqbalance 本该解决这件事
irqbalance 是一个守护进程,它周期性(默认 10 秒)扫描 /proc/interrupts,计算每颗核的中断负载,然后把中断迁移到负载低的核上——这就是"中断均衡"。
但这次它没干活:
- 服务状态
inactive (dead)——irqbalance 没在运行。为什么没跑?Loaded: disabled,服务被设为不随开机启动。可能是安装时没 enable,或某次"优化"里被关掉了。 IRQBALANCE_BANNED_CPUS="fffe"——就算服务启动,配置也把它锁死了:除了 CPU0 的 15 颗核全被 ban。两个问题叠加,中断只能堆 CPU0。
内核版本说明:IRQ 亲和机制(
/proc/irq/*/smp_affinity)在所有主流内核版本通用。irqbalance 的配置文件路径有差异——RHEL/CentOS 是/etc/sysconfig/irqbalance,Debian/Ubuntu 是/etc/default/irqbalance,但IRQBALANCE_BANNED_CPUS变量语义一致。
处方
修复 1:修正 BANNED_CPUS 配置
把误配的 fffe 清掉,让所有核都参与中断均衡:
$ cat /etc/sysconfig/irqbalance
# IRQBALANCE_BANNED_CPUS: 禁止参与中断均衡的 CPU(十六进制掩码)
IRQBALANCE_BANNED_CPUS="" # 空 = 所有核都可参与
修复 2:启动并启用 irqbalance
$ systemctl start irqbalance
$ systemctl enable irqbalance
$ systemctl status irqbalance
● irqbalance.service - irqbalance daemon
Active: active (running)
验证:中断重新分散

修复后 CPU0 的 hi 降到 4%,top 单核输出一目了然:
$ top -1
%Cpu0 : 8.0 us, 1.0 sy, 0.0 ni, 86.0 id, 0.0 wa, 4.0 hi, 1.0 si, 0.0 st
| 指标 | 修复前 | 修复后 |
|---|---|---|
| CPU0 hi% | 95% | 4% |
| 中断分布 | 全堆 CPU0 | 分散到 4 颗核 |
| 网卡吞吐 | 400MB/s | 880MB/s |
| 网关 P99 RT | 80ms | 9ms |
三行配置,不用加机器,不用改代码,网卡吞吐翻倍。
上线前检查清单
| 检查项 | 命令 |
|---|---|
| irqbalance 是否启用 | systemctl is-enabled irqbalance |
| BANNED_CPUS 是否有值 | grep BANNED /etc/sysconfig/irqbalance |
| 中断是否分散 | grep eth0 /proc/interrupts 看多核列 |
| 单核 hi 是否异常 | top -1 看各核 hi 列 |
附:完整命令清单
全量速查(7 步)
# 1. 看单核分布(先确认方向,3 秒)
top -1
# 2. 确认是硬中断还是软中断
mpstat -P ALL 1 # 逐核 %irq vs %soft 列
# 3. 看中断计数分布(哪类中断、落在哪个核)
grep -E 'eth0|ens|em[0-9]' /proc/interrupts
# 4. 看中断亲和性
cat /proc/irq/{46,47,48,49}/smp_affinity_list
# 5. 查 irqbalance 服务状态与配置
systemctl status irqbalance
cat /etc/sysconfig/irqbalance # RHEL/CentOS
cat /etc/default/irqbalance # Debian/Ubuntu
# 6. 修复:清空 BANNED_CPUS + 启动服务
# /etc/sysconfig/irqbalance 中 IRQBALANCE_BANNED_CPUS="" 或删除该行
systemctl start irqbalance
systemctl enable irqbalance
# 7. 验证
grep eth0 /proc/interrupts # 看是否分散到多核
top -1 # CPU0 hi% 应明显下降
下篇我们聊系统调用过多导致 CPU 100%——这一次 CPU 时间全花在内核态,sys 列飙高,strace 定位法登场。
📺 公众号「Ai拆代码的曹操」 🌟 知识星球「Ai拆代码的曹操」