news 2026/10/20 2:56:05

单核软中断过高排查与调优实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
单核软中断过高排查与调优实战指南

凌晨三点,告警群弹出一条消息:某业务网关 CPU 单核软中断打满,请求延迟开始冒尖。打开 top 一看,核心 12 的 si 一直停在 99%,其他核倒是很闲。这种“一个核发热,一群核围观”的场景,做运维的人基本都见过。单核软中断过高,说白了就是网络软中断处理被牢牢压在某个 CPU 上,要么中断绑得太死,要么网卡队列太单一,要么流量均衡没做对。这篇文章就把这套排查思路掰开讲清楚,从看数、定位到动手调优,给出一套能直接复现的操作流程。

这篇内容适合谁看?两类人最需要:一类是刚接手服务器运维、遇到打满但不敢乱动的同学,另一类是已经在线上遇到间歇性抖动、想从根上确认是不是软中断问题的同行。核心解决三个问题:第一,单核软中断高到底是谁在耗 CPU;第二,为什么它会固定出现在一个核上;第三,怎么在不拍脑袋乱调参数的前提下,把负载摊到多个核心上。下面按排查顺序往下走。

1. 先搞清楚:单核软中断过高到底是谁在消耗 CPU

1.1 top/htop 怎么看,别把软中断和硬中断搞混

很多人一看到单核 CPU 高,第一反应是看进程。但软中断消耗的 CPU 不一定归到某个进程头上,它通常表现在内核态消耗和专门的 si 指标上。

普通 top 默认显示的是整体 CPU 汇总,像 us、sy、wa、hi、si 这些字段。其中 sy 是内核态总消耗,hi 是硬中断消耗,si 才是软中断消耗。想要看每个核心的具体情况,在 top 运行界面按数字键 1,就会展开每个 CPU 的逻辑核心列表。这时候你要重点盯 si 这一列。

举个例子,某台机器是 64 核,正常情况下所有核心的 si 都在 1% 到 3% 之间。如果某个核心的 si 长时间在 80% 以上,而它旁边的核心 si 都不到 1%,这就非常可疑。

还要区分一下硬中断和软中断。硬中断由硬件设备直接触发,比如网卡收到数据包后通过中断控制器通知 CPU。软中断则是内核在硬中断返回后,延后处理的机制。网络收包路径上的 NET_RX、NET_TX,定时器 TIMER,块设备 BLOCK 等等,都是常见的软中断类型。你在 top 里看到的 si,通常是这些软中断累积消耗导致的。

判断是不是 ksoftirqd 在代劳,可以用 ps 看:

ps aux | grep ksoftirqd

正常空闲期,ksoftirqd 几乎没有 CPU 消耗。如果某个 ksoftirqd 线程长期占用 CPU,比如 ksoftirqd/12 持续 90% 以上,说明软中断事件已经多到需要内核专门调度一个线程来处理了。这也是单核软中断过高的重要旁证。

1.2 影响评估:什么时候才算“过高”

“高”不是绝对百分比,而是相对业务影响来看。我一般分三个档位:

  • 轻度:单核 si 30% 到 50%,业务无明显波动,可能是瞬时小包突增,观察即可。
  • 中度:单核 si 70% 以上,CPU 该核全核不高,但其他核空闲,此时已经需要介入。
  • 严重:单核 si 持续 90% 以上,伴随请求延迟上升、超时、甚至网卡丢包计数增长,必须立刻处理。

这里的关键不是“一个核很高”,而是“其他核很闲”。如果所有核都跑到 80% 以上,那是整机流量真正打满了,属于扩容问题。但如果只有一个核飘红,其他核大量空闲,那基本就是分配不均,调优空间很大。

还要判断到底是接收路径还是发送路径的问题。网络软中断过高绝大多数是 NET_RX,也就是收包方向的软中断。发送方向 NET_TX 也有可能,但通常配合高并发连接数或特定发送模式出现。后续诊断时你要在 /proc/softirqs 里看清楚是哪个类型在涨。

提示:单核 si 高不完全等于丢包。是否丢包要看网卡统计和内核网络栈的行为。有时即使 CPU 软中断 100%,网卡队列也在正常收包,只是处理延迟拉长;但如果在 softirq 处理不过来时,NAPI 会停止 poll,这时丢包计数就可能开始增长。

2. 定位单点:用实例带你走一遍诊断流程

2.1 场景设定与关键命令

我拿一个实际排过的场景举例。节点是一台 64 核的物理服务器,跑的是业务接入网关,网卡是万兆,流量模型以小包为主。现象是核心 12 的 si 一直 90% 以上,其他核心 si 只有 2% 左右,应用层接口延迟从正常的 3ms 涨到 30ms。

第一步,用 mpstat 确认软中断分布,每 1 秒输出一次,连续看几秒:

mpstat -P ALL 1 3

重点关注 %soft 列。正常情况下每个核的 %soft 应该相对均衡。如果某个核的 %soft 长期独高,说明所有软中断都集中到了这个核。这里我用的是 CPU12,也就是编号为 12 的逻辑核心。

第二步,看 /proc/interrupts,确认是不是网卡中断只落在 CPU12:

cat /proc/interrupts | grep -E 'eth0|ens3|enp'

每一行是一个中断号,后面跟着不同 CPU 上的累计中断次数。如果某一行的数字只在 CPU12 对应列下有增长,其他列基本是 0,说明这个中断号和 CPU12 绑死了。

第三步,看 /proc/softirqs,确认哪种软中断类型在 CPU12 上异常增长:

cat /proc/softirqs

关注 NET_RX、NET_TX、TIMER、BLOCK 这些字段。网络收发相关问题,主要看 NET_RX 和 NET_TX。通过“先看 totals 再看增长率”的方式,可以确定增长来源。比如间隔 5 秒前后各看一次,NET_RX 在 CPU12 上增加的数量是其他核心的几十倍,那就锁定了方向。

第四步,如果方便使用 perf,可以只抓 CPU12 上的热点:

perf top -C 12

通常会看到 net_rx_action、process_backlog、napi_poll 这类函数出现在热点里。到这里,基本可以坐实是网络接收路径上的软中断集中消耗了 CPU12。

2.2 从现象反推根因的判断步骤

锁定 CPU12 之后,就要回答一个问题:为什么偏偏是它?

我的经验是从三个方向同时查:

  • 查中断亲和性。cat /proc/irq/<中断号>/smp_affinity,看位掩码是不是只包含一个 CPU。如果是 1,那就只绑定 CPU0;如果是 2,只绑定 CPU1;如果是 1000,只绑定 CPU12。这个位掩码是十六进制形式,也就是 CPU 位的集合。
  • 查网卡队列能力。ethtool -l <网卡名>,看网卡当前是单队列还是多队列。如果 Combined 只有 1,那大概率是单队列网卡,或者驱动配置没开多队列。
  • 查中断是否长期固定。单纯看一次 /proc/interrupts 不够,要连续看。因为中断可能在多个 CPU 之间跳变。用命令多次采样,看 CPU12 对灾害标记是否每次都增长。

实际排的时候,我在这台机器上看到 eth0 的中断号 78 只在 CPU12 列在不断增长,而且 eth0 的队列数只有一个,其他队列根本没有流量。这就解释出一个看起来很简单的因果链:网卡收包一律进单队列 -> 中断固定触发在一个 CPU -> 软中断随之固化在该 CPU。后续所有调优,都围绕打破这条链展开。

2.3 看增长率和耗时,别被瞬时值误导

只看一次 /proc/softirqs 容易误判,因为有些场景是“计数器本身差异大,但增长率并不夸张”。比如机器刚开机时,某个 CPU 因为启动阶段引导了网卡,中断计数天然就比别的核高。关键还是看增长率。

我习惯用这样的方式采样:

date +%s%N; cat /proc/softirqs > /tmp/softirqs_before sleep 5 date +%s%N; cat /proc/softirqs > /tmp/softirqs_after

然后把两个文件对比一下,算各列增长的 delta。如果 NET_RX 在 CPU12 增长了几十万,其他核只增长几千,这个差距就是核心证据。

还有一个思路值得提:perf sched 或 ftrace 可以看软中断在某个核上的实际占用时间。比如 irqsoff tracer 可以记录中断关闭的时长,softirq tracer 可以观察软中断执行时长。不过日常排障优先级不用放那么高,绝大多数场景通过 mpstat + interrupts + softirqs 三件套就能定位了。

3. 根因剖析:为什么中断就是喜欢“堵”在一个核上

3.1 网卡队列与 IRQ 亲和性

现代网卡普遍支持多队列,也就是把收包分成多个队列,每个队列可以绑定不同的 CPU。这个能力叫 RSS,部分网卡叫 Receive Side Scaling。但支持多队列不等于默认就开了。有些网卡默认 Combined 是 1,有些驱动版本会把队列数限制在很小值,还有些云主机的虚拟网卡在某种配置下只暴露一个队列。

一旦网卡只有一个队列,所有收包都走同一个中断号,这个中断号又默认绑在 CPU 某个核上,单个核的软中断就容易被顶满。这个模式在物理机和云主机环境都很常见。

IRQ 亲和性则进一步控制中断可以由哪些 CPU 处理。系统默认情况下,irqbalance 会尝试把中断均匀分配到各核心。但现实往往是:irqbalance 的算法更重视保持中断归属稳定,避免频繁迁移,所以它会倾向于让一个中断长期停在某个核上。对于高吞吐网卡来说,这种“稳定”反而成了瓶颈。

3.2 RPS/RFS 与 CPU 缓存的问题

如果网卡只有一个队列,还有一个软件层面的办法:RPS,全称 Receive Packet Steering。它可以把一个队列收到的包,通过软件 hash 分发到多个 CPU 上去做软中断处理。它不需要改硬件队列,也不依赖驱动支持,对旧网卡和虚拟网卡特别友好。

RPS 的代价是,原本删掉硬中断对单一 CPU 的局部性优势,现在变成了跨 CPU 分发包,CPU 缓存命中率会下降。因此 RPS 不是把掩码设成所有核就万事大吉。它对大连接数、小包流量的收益更高,对单连接大包流反而可能拿到负优化。

RFS 则是 RPS 的增强版,全称 Receive Flow Steering。它在 RPS 分发的过程中结合了进程所在 CPU 的信息,试图让处理某条 socket 流量的 CPU,和真正读取该 socket 的用户进程在同一个核上。这样做的好处是减少缓存丢失,特别适合某个进程密集处理网络请求的场景。但 RFS 不是免费的,它需要维护 flow table,内存和 CPU 都有额外消耗。

3.3 NUMA 和虚拟化干扰

如果是多路 CPU 的物理机,NUMA 节点的影响比很多人想象中更大。网卡 DMA 环形缓冲区通常分配在网卡所在 NUMA 节点的内存上。如果你把网卡中断绑定到了另一个节点的 CPU,CPU 访问这些 buffer 时就要跨 NUMA 总线,延迟和带宽都会恶化。

所以调软中断分布时,不要只看“哪个核空闲”就绑哪个核,要先看网卡插在哪个 NUMA 节点上。可以通过 lspci 加 numactl 确认,也可以直接 numactl --hardware 看节点拓扑。推荐先把中断绑定到网卡所在 NUMA 节点内的核心,再考虑跨节点扩展。

虚拟化场景更麻烦。云主机你看到的是 vCPU,但底层是宿主机在调度。虽然虚拟机内设置的 smp_affinity 对虚拟中断有一定的分布作用,但底层物理 CPU 的竞争情况肉眼看不到。一个常见反例是:你在宿主机上给 vCPU 绑定了两个核,但这两个 vCPU 其实跑在同一物理核的不同超线程上,最后软中断还是被物理资源卡住。遇到这种情况,先做“把网卡多队列打开 + 尽量把网络处理核分散到不同物理核心”的调整,再结合整机负载判断。

4. 实操解法:一套可落地的软中断均衡方案

4.1 手动调整 IRQ 亲和性

定位到具体中断号之后,第一步就是调整 /proc/irq/ /smp_affinity。先确认当前值:

cat /proc/irq/78/smp_affinity

如果输出 1000,表示中断只允许在 CPU12 上处理。要改成 CPU0 到 CPU3 都能处理,可以写成:

echo f > /proc/irq/78/smp_affinity

这里的 f 是十六进制,对应二进制 1111,就是 CPU0/CPU1/CPU2/CPU3 四个核。

更精细的写法是直接用 smp_affinity_list,用十进制 CPU 列表来设置,可读性更高:

echo 0-3 > /proc/irq/78/smp_affinity_list

注意,这两者是同一组属性,改一个会联动另一个。写进去后立刻生效,不需要重启。

如果网卡有多个队列,通常每个队列对应一个独立的中断号。比如四队列网卡会生成多个 PCI-MSI 中断,名称往往是 eth0-TxRx-0、eth0-TxRx-1 这种。手动一个个改费时费力,一般可以用脚本循环处理。核心逻辑是:把队列按顺序映射到不同的 CPU 上,形成类似 0、1、2、3 的轮转,而不是全部塞给一个核。

重要提示:在改动 IRQ 亲和性之前,先把 irqbalance 停掉或改成只做建议。否则 irqbalance 的定期调整会把你的手动配置冲掉。线上操作时,可以先 systemctl stop irqbalance,调完后再决定是否永久关闭或只排除网卡中断。

4.2 网卡多队列与 ring 参数调优

先确认网卡队列能力:

ethtool -l eth0

如果输出显示 Current 为 1,Possible 为 8,那说明驱动支持多队列,只是当前没启用。可以直接调整:

ethtool -L eth0 combined 8

这个命令会把收发队列合并成 8 个 Combined 队列。对于大部分网卡,这样分配比较合理。如果业务是接收为主,也可以把 rx 和 tx 分开设置,比如 rx 8、tx 4。命令形式是:

ethtool -L eth0 rx 8 tx 4

不是所有网卡都支持这种分离模式,执行后可以先 ethtool -l eth0 确认。

队列数也不是越开越多越好。每个队列都要消耗内存和 CPU 资源,通常是按物理核数的一半到全部来设定。单核软中断过高的场景里,我是先把队列数从 1 调到 4 或 8,再配合中断绑定观察效果,而不是一步直接拉满到几十个队列。

顺带看下 ring buffer 大小:

ethtool -g eth0

如果 RX 的当前值过小,突发的流量会把队列丢在网卡驱动的 ring buffer 上。一般可以调大,比如 2048 或 4096:

ethtool -G eth0 rx 4096 tx 4096

ring 越大,抗突发能力越强,但也会让延迟变得更平滑还是偏高,取决于负载模型。建议在一个可控的测试时间窗内调整并观察丢包指标。

对于云主机来说, # ethtool -L 不一定有效,因为虚拟网卡是否支持多队列,由宿主机的虚拟化网络模型决定。常见云厂商提供的是 virtio 网卡,virtio 本身也支持 multiqueue,但规格限制可能让多个 vCPU 对应多个队列。在虚拟机里面如果 ethtool -l 显示 Possible 就是 1,那就不要死磕网卡队列,直接用 RPS。

4.3 用 RPS/RFS 摊开单队列压力

当网卡队列无法扩展时,RPS 是性价比最高的解法。它的配置入口在 sysfs 的队列目录下:

/sys/class/net/eth0/queues/rx-0/rps_cpus

默认值是 0,表示关闭 RPS。把它设置为让某些 CPU 参与处理:

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

f0 的二进制是 11110000,表示 CPU4、CPU5、CPU6、CPU7 参与收包软中断。注意,CPU 编号的映射是低位对应 CPU0。如果设置成 ff,那就是 CPU0 到 CPU7 共 8 个核。

RPS 的另一个重要参数是全局流表大小:

sysctl -w net.core.rps_sock_flow_entries=32768

单队列开启 RFS 时还需要设置每个队列的流表项数:

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

这里有个容易被忽略的点:rps_sock_flow_entries 是全局 wait,rps_flow_cnt 是每个队列的 wait。Redis hash 的规模要匹配,否则 RFS 的 flow 条目不够用,哈希冲突会导致分发不稳定。一般建议 rps_sock_flow_entries 设成 32768 的整数倍,且总 rps_flow_cnt 总量不超过全局值,否则多余的条目不会生效。

RPS 配置后要及时验证效果。再跑一遍 mpstat -P ALL 1,观察单核 si 是否分散到目标核上。不过 RPS 的收益和流量模型强相关。小包多连接场景收益最明显,单条长连接流量可能效果平平。

4.4 按需微调内核 softirq 预算参数

如果软中断处理本身的调度周期不合理,也可以微调内核参数。最常涉及的是:

net.core.netdev_budget net.core.netdev_budget_usecs net.core.dev_weight

解释一下这批参数。netdev_budget 是 NAPI 每次 poll 最多可以处理的包数量,默认通常是 300。netdev_budget_usecs 是每次 poll 允许的最长处理时间,默认 2000 微秒。dev_weight 与 backlog 处理相关。

当某个核的 NET_RX 软中断过高是因为单次 poll 时间过长,比如一次处理了大量包导致 CPU 长期卡在 softirq 里,可以适当降低 netdev_budget,让处理过程更频繁地切出去,降低单核占用率。但这个参数的负作用也明显:如果流量吞吐要求非常高,预算降低后,一次 poll 能处理的包变少,可能导致吞吐下降或丢包上升。

我自己的经验是:如果不是在压测场景,不建议动不动就调 budget。先用中断亲和性和队列调优解决 80% 的问题。只有当你看到单核 si 长时间 100%,并且 perf top 显示 net_rx_action 占用极大,同时业务对延迟抖动特别敏感时,才考虑把 netdev_budget 从 300 改成 128 这类值,试试对延迟的影响。

sysctl 直接改测试:

sysctl -w net.core.netdev_budget=128 sysctl -w net.core.netdev_budget_usecs=1000

观察几分钟。如果要持久化,写到 /etc/sysctl.conf 或对应的 sysctl.d 目录。改回去也一样,改回 300 和 2000 即可。

此外,如果服务器完全不跑网络重负载业务,也可以考虑给业务进程做 CPU 绑定,把网卡相关中断限制在一个小核组,把关键业务隔离到其他核。这属于更细粒度的部署方案,适合专用的高吞吐网关节点。

5. 常见问题与排查技巧速查

5.1 现象、原因与排查对照表

下面这张表是我在排障时经常对照的,按“看到的现象”去找“可能的原因”,能省不少瞎猜的时间。

现象常见原因排查方向
单个核 si 很高,其他核 si 很低网卡单队列 / 中断亲和性集中ethtool -l、/proc/irq/*/smp_affinity
队列数已经多了,但只有一个队列在涨队列绑定的 CPU 不均匀或哈希不均看 ethtool -S 各队列统计,调整中断绑定
si 高,但 /proc/interrupts 里中断计数不高RPS 或软件处理导致,硬中断不集中看 /proc/softirqs 和 perf 热点
调大 rps_cpus 后没变化RPS 未生效或流量模型不合适确认掩码写入成功,看 CPU 编号映射
开启 RFS 后软中断更高flow table 配置过大/过小,或者跨 NUMA检查 rps_sock_flow_entries 与 rps_flow_cnt
云主机 ethtool -L 不支持虚拟网卡单队列直接用 RPS,不要硬调队列
设置亲和性后一段时间又乱irqbalance 在后台重新平衡停止 irqbalance 或排除指定中断
出现大量 NET_RX drop 计数CPU 处理不过来,NAPI 被关闭观察丢包队列,调大 ring 或增加队列数

这张表不能代替完整排查,但它能把“从何下手”这件事变得非常简单。出现单核软中断过高时,我通常先想一句话:到底是硬中断扎堆,还是软中断处理本身吃不住?前者看 interrupts,后者看 softirqs。

5.2 实战避坑三个细节

第一个坑:绑核不看 NUMA。

很多人看到核心 0 忙,就把中断绑到核心 1,但如果核心 0 和核心 1 分属不同 NUMA 节点,网卡实际在核心 0 一侧的 NUMA 上,跨节点访问带来的开销可能比原来的软中断还麻烦。建议操作前先用 numactl --hardware 和网卡所在 PCI 总线信息确认拓扑,把中断尽量绑定到网卡同节点内的核。

第二个坑:RPS 掩码把 CPU 编号搞反。

RPS 的位掩码最低位是 CPU0,不是 CPU1。设置 f0 表示 CPU4 到 CPU7,这是最常见的易错点。调完之后用 mpstat 确认是不是目标核在涨,而不是想当然以为 f0 就是 CPU8 到 CPU15。这个低级错误会导致你把流量堆到另一个意想不到的核上,反而加剧问题。

第三个坑:顺手把所有参数调“大”。

有些同行一看到单核高,就同时加大 ring buffer、增加 netdev_budget、把 RPS 掩码撒到全核,结果延迟反而更糟糕。调优要一个变量一个变量来:先改队列和中断亲和性,观察 5 分钟;没效果再上 RPS;RPS 不够再动 budget。每改一步都要对比延迟、丢包、si 占比三个指标。改完后保留变化记录,方便回退。

最后再分享一个收尾动作。问题解决后,我会把最终的队列分布、中断绑核、RPS 配置写成一份小的变更记录,和监控图标截图放一起存档。这不是走形式,而是因为下次真出现类似问题,对比变更前后,你就能迅速知道到底是不是调优带来的副作用。软中断问题看似复杂,但只要抓住“分配均不均”和“处理能力够不够”这两条主线,就不会漫无目的地瞎试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 2:55:46

PyCharm安装通义灵码全攻略:AI编程助手实战与避坑指南

最近总有人问我&#xff0c;PyCharm 里到底怎么装通义灵码&#xff0c;装完之后怎么用 AI 写代码才有实际意义。说实话&#xff0c;顺手装个插件很简单&#xff0c;难的是装完之后怎么让它融入你的开发流程&#xff0c;而不是像某个社交软件一样偶尔打开看一眼。我自己在 PyCha…

作者头像 李华
网站建设 2026/10/20 2:55:28

C#桌面端ListView数据绑定实战:数据库查询搜索与右键新建文件

1. 桌面端数据管理的经典命题&#xff1a;为什么还要聊ListView绑定做桌面端开发的朋友大概率都碰过这样的场景&#xff1a;一个内部用的资料管理工具、一个设备台账系统、或者一个简单的文件归档面板&#xff0c;需求方给的要求往往很朴素——“左边列个表&#xff0c;能搜&am…

作者头像 李华
网站建设 2026/10/20 2:54:21

托管式Agent实战:把AI团队管理变成一键服务

上个月我在做一个偏研究类的自动化任务&#xff0c;自己用某套大模型接口搭了一个多步工作流&#xff1a;先检索资料、再归纳、再写报告。一开始跑得很顺&#xff0c;等任务变多就暴露了问题——循环失控、上下文越跑越乱、失败重试要自己写。我那时候就在想&#xff0c;要是平…

作者头像 李华
网站建设 2026/10/20 2:53:09

从顺序表到链表:线性表核心原理与C++实现

1. 两种存储选择背后的根本矛盾想真正理解线性表&#xff0c;得先想明白一个问题&#xff1a;我们为什么要发明"线性表"这种数据结构&#xff1f;其实答案特别朴素。现实世界里大量数据天生就是"一列带顺序的东西"——一个班的学生名单&#xff0c;一排货架…

作者头像 李华
网站建设 2026/10/20 2:53:09

SpringBoot+Vue养老公寓管理系统:源码部署与二次开发实战

市面上打着“完整源码部署教程”旗号的项目资源很多&#xff0c;但真正能让人从零跑通、看懂、改得动的其实不多。这套前后端分离的夕阳红公寓管理系统&#xff0c;用的是SpringBootVueMyBatisMySQL这套非常经典的组合&#xff0c;恰好覆盖了大多数课程设计和中小型实战项目的技…

作者头像 李华
网站建设 2026/10/20 2:53:08

蒙皮计算的空间基准:为什么顶点在模型空间而骨骼在世界空间

上周帮同事调一个角色模型&#xff0c;T-Pose下骨骼明明好好“长”在网格里&#xff0c;可一到运行时蒙皮就炸开&#xff0c;手臂直接甩到世界另一边。折腾了大半天&#xff0c;最后定位在导出设置上——网格顶点一直待在它自己的局部坐标系里&#xff0c;而骨骼的变换却被记录…

作者头像 李华