☰
Linux单核软中断过高排查与调优实践:从定位到解决
2026/10/20 2:56:05 网站建设 项目流程

设备单核软中断过高,是运维排查里非常典型的一类问题。很多刚接触这块的朋友,第一次看到top里某个 CPU 核心的si飙到 80%、90%,第一反应是“是不是机器中毒了”,或者“是不是 CPU 太差该扩容了”。其实大多数情况下,这两个判断都不对,真正的问题往往出在内核处理网络数据包的方式上。这篇文章就围绕这个场景,把我实际排查下来的一套思路和操作记录下来,希望能给遇到类似问题的你一个参考。

先说清楚这篇文章适合谁看:正在被“单核 CPU 使用率莫名飙高”困扰的运维工程师、刚入门云计算或 IDC 运维的读者,以及自己做服务器、对系统性能调优感兴趣的开发者。看完之后,你至少能掌握一套从定位、分析到解决问题的完整方法,并且知道每一步操作背后的原理,而不是只会照着命令敲。

那我们就直接从“软中断”到底是个什么东西开始说起。

1. 铺垫与认知:单核软中断高,到底在“高”什么

1.1 先分清“硬中断”和“软中断”

要搞明白软中断过高,得先知道 Linux 内核处理网络数据包的流程。你可以把内核想象成一家快递分拣中心,硬中断和软中断是两条配合工作的流水线。

硬中断(Hard IRQ)由硬件设备(一般是网卡)触发。当网卡收到数据,会告诉 CPU“有货到了,赶紧处理”,这个信号就是硬中断。硬中断的特点是处理速度必须非常快,因为它会打断 CPU 当前的工作,所以硬中断里通常只做最紧急的事情——把数据从网卡的接收队列搬进内存,然后马上告诉内核“后续工作交给软中断”。

软中断(Soft IRQ)是内核在特权模式下执行的、可延迟处理的中断任务。网卡的数据搬进内存后,真正繁重的处理——比如解析协议栈、判断数据包去向、交给上层应用——都是在软中断里完成的。top命令里的si字段,指的就是 CPU 花在软中断上的时间占比。

这里有一个非常关键的特性:软中断的负载通常会被内核固定分配给某几个 CPU 核心,而不是像普通进程那样在多个核心间动态平衡。这就是为什么你会看到整机负载不高,但某一个核心的si高得离谱。换句话说,当单核软中断飙高时,本质上是软中断的负载分布出了问题,把压力集中在了某个特定的核心上。

1.2 你看到的“单核高”,其实有三种常见假象

在实际告警中,单核高不完全等价于软中断高,需要先区分清楚。我遇到过三种经常被混淆的情况:

第一种,确实是软中断高,top里%si那一栏非常高,比如超过 60% 甚至 80%。这种情况多半和网络流量有关,是本文要重点讲的。

第二种是软中断不高,但某个核心的整体使用率非常高,%us或%sy很高。这种情况可能是某个跑在单线程上的应用进程导致,也可能是有大量系统调用集中在某一个核上。

第三种是%si和%hi(硬中断)同时高,这往往意味着硬件中断本身已经出现了严重的负载不均,比如网卡的多个队列没有合理分散到不同 CPU。

排查的第一步,其实就是先通过几秒钟的观察确认到底属于哪一种,避免一上来就被“单核高”带偏方向。

1.3 网络软中断在内核里主要做什么

为了后续排查时能有针对性,这里简单提一下网络相关的软中断类型。在cat /proc/softirqs的输出里,你会看到NET_RX、NET_TX这类字段。

  • NET_RX:网络收包软中断,这是最常见的高负载来源。
  • NET_TX:网络发包软中断,通常压力比收包小很多,但高并发小包场景下也可能成为瓶颈。
  • BLOCK:块设备相关的软中断,如果读写压力很大也可能出现。
  • RCU、SCHED等:这些在某些内核版本里也可能表现为软中断时间,但通常不是单核飙高的大头。

明白这一点有什么用?如果打开/proc/softirqs发现某个 CPU 核心的NET_RX数值比其他核心高出几个量级,那问题就基本锁定在网络收包路径上了。接下来要做的,就是沿着这条路径去定位具体是谁在“喂”这个核心这么高的收包负载。

2. 排查定位:用一套标准动作确认问题根源

2.1 第一步:top 观察,识别可疑的核心与进程

收到单核软中断过高的告警后,我习惯先执行这么一条命令,盯上十几秒:

top -d 1

重点看两样东西:进程列表里有没有异常高的进程,以及 Cpu 那一行的si占比。如果si本身并不高,但某一个进程的 CPU 占用几乎吃满单核,问题多半在应用层,不是软中断。

如果si确实高,下一步就是确认是不是集中在某一个核上。top默认显示的是总体 CPU 使用率,要单独看每个核,可以在top运行界面按数字键1,展开所有核心的实时使用率。此时你可能会看到类似这样的输出:

%Cpu0 : 85.0 si %Cpu1 : 2.0 us %Cpu2 : 1.0 us %Cpu3 : 3.0 si

Cpu0 的si明显是热点。到这里,基本可以确认问题形态是“单核软中断高”,接下来用mpstat做更精确的确认。

2.2 第二步:mpstat 确认单核软中断占比

top是看了一眼全局,mpstat则是更精确地去量化每一个 CPU 核心的时间分配。执行:

mpstat -P ALL -I CPU 1 5

这个命令的含义是:每 1 秒输出一次所有 CPU 核的状态,总共输出 5 次。对应参数分别是-P ALL表示所有核心,-I CPU表示输出 CPU 时间分配,最后的1 5是采样间隔和次数。

输出里%soft列直接显示软中断占该核心时间的百分比。如果某个核的%soft稳定在 50% 以上,而整机其他核的%soft都很低,就可以正式下结论:存在单核软中断热点。

到这一步还不够,我们还要弄清楚软中断具体是哪种类型、增长有多快。这个时候就要用到watch配合/proc/softirqs了。

2.3 第三步:softirqs 统计文件——锁定具体中断类型

watch -d -n 1 'cat /proc/softirqs'

-d参数会把两次输出之间发生变化的内容高亮显示,非常方便观察。重点看NET_RX对应的一行,可以看到它在每个 CPU 核心上的累计次数。

假设你看到的数值是这样的:

CPU0 CPU1 CPU2 CPU3 NET_RX: 582103452 128983 102989 3028271

CPU0 的累计值比 CPU1 和 CPU2 高了几个数量级,同时它在观测过程中还在快速往上跳。这说明网络收包的中断处理几乎全部压在 CPU0 上,而其他核心并没有分担。

为什么会这样?这里要引出下一个关键问题:网卡的多个队列和 CPU 中断亲和性是如何配置的。

2.4 第四步:观测网络吞吐特征,判断流量类型

在深入分析内核配置之前,我还会快速看一眼当前网络流量的特征。执行:

sar -n DEV 1 5

或者用iftop这样的工具看实时流量。这一步的目的是回答三个问题:

  1. 当前流量是持续大流量,还是周期性脉冲?
  2. 包量(PPS)高还是吞吐(BPS)高?小包攻击通常是 PPS 极高、BPS 不高。
  3. 流量来源是全局限速还是某一台机器?

搞清楚这三点,能帮你快速判断问题的性质。如果只是某台机器在往外疯狂发包造成的,那解决问题优先级可能是先处理来源,而不是先调优内核参数。如果是正常业务流量但 PPS 很高,则是典型的软中断负载分布问题,需要从网卡队列和 CPU 绑定的角度去解决。

到这里,排查链路已经推进到了内核和网卡层面,下一步就是具体的方案选型和操作。

3. 核心方案与实操过程:把软中断负载从单核上“散开”

这一节是整个排查的核心,也是实际工作中真正动手的部分。我会按照操作的优先级顺序来写,从最简单的检查开始,逐步深入到底层配置。每一步都会说明为什么要这么做,避免你照着网上的代码一顿敲却不知道在干什么。

3.1 方案选型:为什么优先调中断亲和性而不是加机器

一说到单核忙不过来,很多人第一反应是升级 CPU 或多加一台机器。但在软中断的场景里,这往往是浪费钱的做法。软中断和硬中断不一样,它是内核内部的处理流程,理论上可以被任何 CPU 核处理,关键就看内核怎么分配。

所以正确思路是:让“处理软中断的能力”尽可能多地铺开,让多个核心分担压力。这就涉及两类核心机制:一是内核自带的自动平衡机制(irqbalance),二是手动指定网卡中断和软中断落在哪些 CPU 上。

好消息是,在多数现代服务器上,我们并不需要重编译内核,也不用换硬件,只是调整几个系统配置就能把单核的压力摊到四核、八核上去。这也是为什么我把方案选型放在“调优配置”而不是“扩容”上。

3.2 检查网卡队列:是否只有单队列在硬扛

先看网卡有几个队列,这是决定后续操作的基础。使用ethtool:

ethtool -l eth0

参数-l表示查看网卡的队列数量(queue)。如果输出类似:

Channel parameters for eth0: Pre-set maximums: RX: 0 TX: 0 Other: 1 Combined: 4 Current hardware settings: RX: 0 TX: 0 Other: 1 Combined: 1

Combined的当前值是 1,说明网卡实际在使用单队列收发数据。这种情况下,所有网络收包的压力都压在一个队列上,而一个队列通常会绑定在一个 CPU 上,这就是单核高最直接的原因。

对应解法很明确:把队列数目增加。执行:

ethtool -L eth0 combined 4

这里-L表示设置队列参数,combined 4是把收发队列合并设为 4 个。设置完之后再确认一次:

ethtool -l eth0

如果显示Current hardware settings: Combined: 4,说明多队列已经生效。

这里要补充一个知识点:并不是所有网卡都支持调整队列数,尤其是一些低端虚拟网卡或老型号物理网卡,-l输出的最大值可能就是 1,甚至不支持ethtool -L。这时候就要靠 3.4 节讲的 RPS(Receive Packet Steering)方案,在软件层面模拟多队列效果。

3.3 检查并配置 CPU 亲和性:让队列均匀分散到核心

网卡有了多队列还不够,还要确认这些队列的中断具体落在哪些 CPU 核心上。查看方式:

cat /proc/interrupts

找到网卡对应的中断号,比如eth0对应的是一组中断号,它们的分布能直接看出负载分配情况。如果所有高数值都集中在 CPU0 上,说明队列和 CPU 的绑定关系需要手动调整。

调整中断亲和性的常用工具是irqbalance。它是一个常驻服务,负责把中断请求自动均衡地分散到各个 CPU 核心上。先看它是否在运行:

systemctl status irqbalance

如果它已经运行了,但你的单核软中断依然很高,说明自动均衡没达到预期,可能是中断绑定被某些规则锁死,也可能是irqbalance运行在内核不支持的场景下。此时可以考虑关掉自动均衡,改用smp_affinity手动指定每个中断号落在哪个 CPU 上。

手动配置的具体做法是修改/proc/irq/{中断号}/smp_affinity文件。比如网卡的中断号是 68-71,四个队列对应四个中断号,我想让它们分别落在 CPU0、CPU1、CPU2、CPU3 上,可以这样执行:

echo 1 > /proc/irq/68/smp_affinity echo 2 > /proc/irq/69/smp_affinity echo 4 > /proc/irq/70/smp_affinity echo 8 > /proc/irq/71/smp_affinity

注意这里写入的是十六进制掩码,1代表 CPU0,2代表 CPU1,4代表 CPU2,8代表 CPU3。想确认是否生效,再查看/proc/interrupts,会看到中断次数开始在多个核心上分布。如果在虚拟机里,这个配置对虚拟网卡不一定生效,需要结合宿主机的能力,这也是为什么前面要先确认队列能力和虚拟化环境。

3.4 软件层兜底方案:开启 RPS / RFS 调度

如果你的网卡不支持多队列(ethtool -l最大值是 1),或者你用的是云服务器、虚拟网卡,没法直接调整队列数,那就需要走 RPS/RFS 路线了。

这里简单解释一下 RPS 和 RFS 的区别:

  • RPS(Receive Packet Steering):在软件层面实现“把一个网卡队列的收包软中断处理分散到多个 CPU”。它的本质是把网卡收到的数据包重新散列(hash)到不同的 CPU 上,让一个队列的收包压力由多个核共同分担。
  • RFS(Receive Flow Steering):基于 RPS 之上的优化,它会考虑应用程序实际运行在哪个 CPU 上,尽量让处理数据包的 CPU 和读取数据的应用 CPU 一致,减少缓存失效带来的性能损失。

启用 RPS 的实操步骤,是向/sys/class/net/{网卡名}/queues/rx-{队列号}/rps_cpus写入一个 CPU 掩码。比如把 eth0 第一个接收队列的 RPS 分布到 CPU0-3 这四个核上,CPU 掩码为二进制1111,也就是十六进制的f:

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

如果队列有多个,比如 rx-0、rx-1,每个都执行一遍。更合适的做法是把所有 CPU 核都参与进来,比如 8 核机器就用掩码ff。

RFS 还需要额外的配置,即设置流表大小。执行:

echo 32768 > /proc/sys/net/core/rps_sock_flow_entries echo 32768 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

这里rps_sock_flow_entries是全局的 socket 流表条目数,rps_flow_cnt是每个队列的流表大小。一般建议设置为32768,实际场景中可以按并发连接数进行调整。如果不设置,RPS 已经能分摊压力,但 RFS 的命中率不高,因此并不强制。

我个人的经验是:纯虚拟网卡环境下,单改 RPS 往往就能把单核软中断从 90% 拉到 30% 以下,效果的瓶颈反而在宿主机物理核数。所以如果你发现自己调完后效果不明显,先确认一下 CPU 是不是被超分(vCPU 比物理核多得多),这种情况很多是邻居效应和宿主调度导致的,调 RPS 也救不了。

3.5 内核参数调优:这几个项是低成本高收益的

如果做完队列调整和 RPS 之后,软中断仍然偏高,还可以从内核参数层面做进一步优化。有三个参数是我在排查中常用的,它们分别作用于网络协议栈的不同阶段。

第一个是net.core.netdev_max_backlog。它表示网卡驱动在接受数据但内核协议栈还没处理时,允许积压的数据包数量。高 PPS 场景下,默认值(通常 1000)可能太小,导致丢包,并且因为丢包重传进一步加剧 CPU 压力。建议调大到 30000 以上:

sysctl -w net.core.netdev_max_backlog=30000

第二个是net.core.somaxconn和net.ipv4.tcp_max_syn_backlog。这两个参数主要影响 TCP 连接的建立队列长度。当大量短连接同时建立时,半连接队列和全连接队列容易溢出,溢出后内核会丢弃 SYN 包,客户端重传又会引发新的软中断负载。把它们的值调大,能显著减少这种“重传风暴”:

sysctl -w net.core.somaxconn=32768 sysctl -w net.ipv4.tcp_max_syn_backlog=65536

第三个是net.ipv4.tcp_tw_reuse相关配置。短连接场景下,TIME_WAIT 状态的 socket 会大量消耗内核资源,虽然它不像前两个参数那样直接作用于软中断,但不及时回收会影响新连接建立,间接导致连接反复重试。如果你的业务允许,可以开启复用:

sysctl -w net.ipv4.tcp_tw_reuse=1

这些参数为什么能缓解软中断单核高?因为它们降低了协议栈内部的排队和重传压力,减少了 CPU 做无用功的次数。换句话说,软中断处理的数据包变少了,CPU 自然就闲下来了。

3.6 实操中的注意事项与临时调整原则

这里必须强调一个操作习惯:上面所有sysctl -w的修改都是临时生效,重启后就会丢失。如果经过测试确认某个参数有效,记得写入/etc/sysctl.conf持久化。但要注意,sysctl.conf是全局配置,改动影响整机所有网络行为,别在核心业务跑着的时候一次性改太多,尽量每次只改一项,观察 5-10 分钟再动下一项。

队列相关的配置,比如ethtool -L,也是重启后失效的。要固化下来,得放到开机自启脚本,或者通过 systemd 自定义一个 service 来执行。这一步经常被新手忽略,等到机器重启后问题复现时才想起来之前调过。

提示:在修改中断亲和性和队列绑定前,最好先执行smp_affinity的备份,把改动前的值记录下来。尤其是有多块网卡的机器,不同网卡中断号不同,修改出错可能导致网络中断,到时候影响面就大了。

4. 常见问题与排查技巧实录

这个主题说难不难,但因为涉及内核、硬件、驱动三层,实际操作时会遇到各种“你以为对但其实不对”的情形。我把这些年踩过的坑整理成几个典型场景,供你排查时对照。

4.1 中断都已经绑到多核了,为什么软中断还是集中在 CPU0

这个问题我遇到过好几次,排查到最后发现是irqbalance在“帮倒忙”。它虽然想把中断分散开,但某些硬件中断的分配策略并不是按smp_affinity的掩码执行的,导致最终收包还是落在 CPU0 上。

此时可以试一试直接把irqbalance停掉,然后手动设置亲和性:

systemctl stop irqbalance systemctl disable irqbalance

然后按 3.3 节的方法手动设置每个队列中断的掩码。这一步操作后往往会立刻看到/proc/interrupts里的数值开始向多个核心扩散。

不过要提醒一句:如果机器上有大量其他类型的中断(比如磁盘中断),关掉irqbalance后需要手动管理所有设备的亲和性,否则可能引发新的不均衡。如果只是临时攻坚,建议先停掉观察,等确认问题解决再决定是否回归。

4.2 为什么 PPS 不高但软中断很高

有一种常见情况:业务吞吐只有几百 Mbps,理论上远没跑满网卡,但软中断已经爆了。这种场景的典型原因是“小包过多”。

举个例子,两个服务之间互相发送海量的小数据包(比如日志、心跳包),单个包只有几十字节,网卡负载看起来不高,但对内核来说,每来一个包就要走一遍完整的协议栈处理流程,CPU 的消耗是按“包个数”计算的,不是按字节数。所以 PPS 高、BPS 不高时,软中断压力依然很大。

应对思路是三管齐下:调大netdev_max_backlog、开启 RPS、以及查一查应用层是否有办法合并小包(比如调整发送缓冲、批量发送)。如果确认是异常流量,还需要抓包定位来源。

4.3 进入了某个核心后一直降不下来,重启也无效

有的机器重启后单核高立刻复现,这通常不是偶发问题,而是“配置没固化”。比如ethtool -L设置的队列数、rps_cpus的掩码、sysctl的参数,重启后全部恢复默认,问题自然回归。

另一个容易忽略的因素是 BIOS 或虚拟化平台的配置,比如物理机开启了 NUMA 且内存分配不均衡,会导致某个 CPU 访问内存的延迟偏高,处理软中断时表现与众不同。在物理机上排查时,可以检查numactl --hardware的节点分布,观察中断是不是都落在某一个 NUMA 节点上。如果是,可以用numactl把相关进程绑到对应节点,同时把网卡中断也绑定到同一节点,避免跨节点访问内存。

4.4 有没有必要换内核版本或升级驱动

当你发现网卡队列调不了、RPS 也开了但效果不理想时,还有一种可能是驱动层把这些功能“截胡”了。某些厂商的网卡驱动自带一套 RSS(Receive Side Scaling)逻辑,它和内核的 RPS 是叠加关系,但实现上并不完全兼容。

这种情况下,升级网卡驱动或者内核版本确实有可能解决问题。我遇到过一例比较旧的网卡驱动不支持多队列的场景,升级厂商驱动后,ethtool -l的最大队列数直接翻倍,软中断高的问题迎刃而解。但升级驱动有风险,尤其是影响网络栈的网卡驱动,操作不当可能导致网络中断。建议先在测试机器上验证,并准备好 iLO/IPMI 或带外管理通道,以便出问题时能恢复。

4.5 快速自检清单

为了方便排查时对照,我把整个流程浓缩成一个自检表:

检查项命令或文件预期结果
确认单核软中断热点top+ 数字键 1 /mpstat -P ALL 1某一核%soft显著偏高
查看软中断类型分布cat /proc/softirqsNET_RX在特定核上数值反常
网卡队列数ethtool -l eth0Combined值 > 1
中断实际分布cat /proc/interrupts多个中断号分散在不同 CPU
CPU 亲和性配置/proc/irq/{irq}/smp_affinity掩码按预期分散
RPS 配置状态cat /sys/class/net/eth0/queues/rx-0/rps_cpus多位掩码而非0
内核排队参数sysctl net.core.netdev_max_backlog大于默认值的合理数值
流量属性确认sar -n DEV 1 5区分 PPS 高还是 BPS 高

这个表做到心里有数,再遇到单核软中断高,照着从上往下检查,基本能定位到根因。

5. 写在最后的经验沉淀

这套流程反复用了很多次之后,我最大的体会是:单核软中断高,绝大多数时候不是硬件不行,而是内核把网络处理任务集中到了单个 CPU 上。很多新人习惯性地先去查业务进程,折腾半天没结果,其实换个思路,先看软中断分布,问题往往几分钟就能定位。

另外一个很深的感受是:操作前一定要做好变更记录。改队列、改中断亲和性、改内核参数,每一步影响范围都不同,特别是云环境里,宿主机和虚拟机间的调度策略我们是看不见的,很多配置调了未必生效,得靠实际观察来判断。所以我建议每做一步调优,都用mpstat和/proc/softirqs对比调整前后的数据,量化效果,而不是凭感觉说“好像好了一点”。

最后再分享一个小技巧:如果你在排查时不确定是不是软中断引起的,可以先观察一段时间的/proc/softirqs增长速率。如果NET_RX每秒增长几万甚至几十万,且集中在单核,那基本上不用再猜了,直接按照第 3 节的方案操作即可。先把队列铺开,再看软中断分布,一套流程走完,大概率就能把告警压下去。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询