凌晨两点,告警群弹出一条:api-gw-03 CPU 使用率 100%,已持续 10 分钟。我连上机器,top 一敲,第一眼就愣住了——%us 几乎为零,倒是 %si 红得发紫,稳稳压在 90% 上下。
按直觉,业务代码再能跑,吃满的也该是用户态的 us。现在倒好,烧 CPU 的是内核自己。这篇文章就把"CPU 100% 但跟你代码无关"这条排查链路,从头到尾走一遍。
报警之后,先把 top 那一行看懂
先别急着找进程,把 top 顶部这行认清楚,后面全靠它指路:
%Cpu(s): 1.0 us, 4.0 sy, 0.0 ni, 4.0 id, 0.0 wa, 1.0 hi, 90.0 si, 0.0 st
八个字段,各有各的活儿:
us(user):用户态,你的业务代码真正在跑的时间。sy(system):内核态,系统调用这类内核替进程干的活。ni(nice):被nice调过优先级的用户态进程。id(idle):空闲,啥也没干。wa(iowait):在等 IO,CPU 其实闲着,只是被磁盘拖住。hi(hardware interrupt):硬中断时间。si(software interrupt):软中断时间。st(steal):被宿主机"偷走"的时间,只有虚拟机/云主机才看得到。
这里要分清一点:sy、hi、si 是三个并列的字段,合起来才是完整的内核态时间。硬中断是网卡、磁盘这些硬件敲门;软中断是内核延迟处理的收尾活儿,比如网络收包就是走软中断。
我们这台机的现场是 si 90.0、sy 4.0、us 1.0。谁在烧 CPU,已经写在脸上了:软中断,跟业务代码半毛钱关系都没有。这时候去优化业务代码、调 JVM、加缓存,全是白费力气。
从进程到线程,一路往下钻
si 高,先确认是不是真的没有"普通进程"在捣乱。top 按 P 排序,或者上 pidstat(sysstat 包里,CentOS/RHEL 用 yum install sysstat,Debian/Ubuntu 用 apt install sysstat):
pidstat -u 1 5
一秒一条,连出五条。你会看到一个有意思的现象:没有哪个进程的 %CPU 特别高,整机却已经打满。这正是软中断打满的典型特征——软中断不算在任何普通进程头上,pidstat 自然看不见。
唯一可能冒头的,是一个叫 ksoftirqd/N 的内核线程。软中断持续爆量时,内核会把处理活分摊给这些内核线程,它们会在 top 里占一点 %sy。这又是一个"内核在忙"的信号。
进程层查不出元凶,往下钻线程也没用——因为问题根本不在进程。这时候别死磕进程了,直接请 perf 出山。
perf 出马,热点直接指向内核
perf 是内核自带的剖析工具,能告诉你 CPU 把时间花在哪些函数上。装法:CentOS/RHEL 是 yum install perf,Debian/Ubuntu 是 apt install linux-tools-common linux-tools-$(uname -r)。
看全系统热点:
perf top
交互界面实时刷新,按占用排序。如果一大排 __do_softirq、net_rx_action 这样的内核函数霸榜,基本可以宣判了:CPU 是被网络收包软中断烧掉的。
提醒:
perf要看内核函数,要么用 root 跑,要么把/proc/sys/kernel/perf_event_paranoid调低。这个值不同发行版默认不一样(常见是 2 或 3),以cat /proc/sys/kernel/perf_event_paranoid实际输出为准。生产环境动它要谨慎,测完记得改回去。
热点函数指向软中断,再对照两个内核文件把证据坐实。
根因:网卡中断全挤在一个核上
/proc/interrupts 每一列是一颗 CPU,每一行是一个中断源。看网卡那一行:
cat /proc/interrupts | grep eth0
如果某个中断的计数全堆在 CPU0 那一列、其他列几乎为零,说明网卡的中断始终只打给一个核。核再强,也扛不住单点软中断的狂轰,si 自然就满了。
再看软中断明细:
cat /proc/softirqs
NET_RX 这一行的计数,同样会高度集中在那一个核上。
到这里根因闭环了:网卡中断(以及它牵动的收包软中断)没有在多个核之间均衡,全都压在一颗核上,把内核态 CPU 打满。
怎么解?三个方向,从软到硬:
一、多队列网卡,把队列开满
先看网卡支持几个队列:
ethtool -l eth0
Combined 那一项如果是 4 或 8,而当前只用了 1,就把它开满:
ethtool -L eth0 combined 4
多队列意味着多路中断(eth0-rx-0、eth0-rx-1……),天然分散到不同核。这要网卡驱动支持 RSS,绝大多数千兆/万兆服务器网卡都没问题。
二、手动绑中断亲和
/proc/irq/<IRQ号>/smp_affinity_list 可以指定这个中断交给哪些 CPU:
echo 0-3 > /proc/irq/84/smp_affinity_list
把网卡每个队列的中断,分别绑到不同的核上。IRQ 号从 /proc/interrupts 里查。
危险警告:改中断亲和、改队列数都会瞬时影响网络收发,务必在低峰期操作,改一条验证一条,别在生产高峰期一把梭。而且这些修改重启后就失效,要持久化得写进网卡配置或启动脚本。
三、RPS,单队列网卡也能分到多核
有些老网卡只有单队列,硬件层面分不了,就用软件补——RPS(Receive Packet Steering)把收包软中断哈希到多核:
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus
f 是十六进制位掩码,代表 CPU0-3。这样单队列网卡也能把软中断摊开。
还有个容易忽略的坑:不少发行版默认开着
irqbalance服务自动分配中断,但多队列网卡上它有时会帮倒忙,把几个队列的中断又挪回同一颗核。遇到反复抖动,先systemctl stop irqbalance再手动绑试试。
三招下去,top 再看,si 从 90% 掉回个位数,软中断散到多核,us 该多高多高——问题彻底消失。
复盘:别只会盯一条 CPU 曲线
事后总结,这次最值钱的是两件事。
第一,监控要拆开看。告警不能只挂一条"CPU 使用率",要把 us、sy、si、wa 分开采集。同样是 100%,us 高是业务问题,sy/si 高是内核与中断问题,wa 高是 IO 问题——三条路完全不同,合并成一条线就把线索淹了。
第二,遇到 si 高,先别碰代码。顺序永远是:先看 %Cpu 哪一类高 → 是软中断就走 pidstat 确认"没有进程高"、perf top 看内核热点 → 再用 /proc/interrupts、/proc/softirqs 定位到中断分布。代码永远是最后才动的。
留一套顺手命令,贴墙上,下次凌晨再遇到照着走:
top # 先看 si/sy/wa 哪个高
pidstat -u 1 5 # 确认是不是没有进程在烧
perf top # 看内核热点函数
cat /proc/interrupts # 看中断是否集中在一个核
cat /proc/softirqs # 看软中断分布
CPU 飙高从来不可怕,可怕的是没分清"谁在烧"。看懂了 top 那第一行,你就已经赢了一半。
本文链接:https://kinber.cn/post/6794.html 转载需授权!
推荐本站淘宝优惠价购买喜欢的宝贝:

支付宝微信扫一扫,打赏作者吧~
