☰
单核软中断100%?Linux网络性能排查与RPS/irqbalance优化实战
2026/10/20 6:23:52 网站建设 项目流程

1. 现象与第一反应:单核软中断高到底是怎么回事

1.1 一个典型的故障现场

值班那阵子,告警群突然弹出一条消息:某台承载网关代理业务的机器,CPU 使用率报警阈值被突破了。上去第一眼,top按 1 展开的柱状图非常刺眼——16 个核里,核 0 直接红透,si(软中断)那一栏几乎占满了整个核,而旁边的 15 个核全部处于半空闲状态,谁也帮不上忙。

业务侧的表现更直接。这台机器平时接口 p99 都在几毫秒量级,那天直接飙到上百毫秒,部分请求开始超时,调用方不得不触发重试,重试又带来新的流量,形成一个恶性循环。像这种"一个核忙死、其它核围观"的局面,在我印象里几乎每次都是同一个方向的问题:要么是网卡队列不够,要么是中断没有均匀铺开,要么是内核把某些类型的软中断全部压在了同一个核上。而不是业务代码真的把 CPU 吃满了——因为只有一颗核在忙,其它核闲着,业务如果真遇上热点,通常是多核一起高。

第一轮做基础确认,我习惯用三条命令并行观察:mpstat -P ALL 1看每个核的%softint占比,vmstat 1看中断和上下文切换的总体水位,再配合top展开所有核。几秒钟数据出来,基本就能确定:问题出在软中断分配,而不是进程 CPU 占用。注意这里有个小坑,top的si因为刷新粒度和叠加算法的问题,短时间窗口里有可能偏高或偏低,不能光靠top下结论,要以mpstat连续采样为准。

1.2 为什么一个核打满会导致整体卡顿

很多人第一反应是:16 个核,某个核打满了,不是还有 15 个闲着吗,怎么会影响整机性能?问题恰恰出在这里。网络收包路径是被某个核"串行化"处理的:数据包到达网卡,触发硬中断,硬中断只是快速标记,紧接着同一颗 CPU 要继续执行对应的软中断,把包从环形缓冲区里取出来,走完驱动层、协议栈、路由查找,最后塞进 socket 队列。这一整条链路必须在发起硬中断的那颗核上推进,其它核帮不上忙。

当这颗核已经满负荷时,新到达的包只能排队等待。更要命的是,中断事件持续不断地到来,CPU 越忙,未处理的软中断越积越多,内核被迫唤醒ksoftirqd/N内核线程来协助,一个核上的上下文切换和调度开销也跟着飙升,处理能力反而进一步下降。这个时候从监控上看是"一个核高",实际上整条网络链路的吞吐和延迟都在恶化,业务侧感受到的往往是系统整体变慢。

所以单核软中断高,本质上不是简单的"核间不均衡"问题,而是系统的中断处理能力被卡在了单个串行点上。这也是后面所有排查和修复的共同目标:想办法把软中断负载平均且可控地分布到多个核上,让每一个核都在干活,而不是看着一个核累死。

2. 软中断机制速览:NET_RX、TIMER、RCU 都在忙什么

2.1 硬中断、软中断与 ksoftirqd 的关系

先理顺一个基础模型。硬件设备发来的中断叫硬中断,它的处理路径要求又快又短,因为在硬中断处理期间 CPU 是不允许被抢占的,时间一长整个系统就像被按了暂停键。所以硬中断只负责"快速确认事件、打标记",真正繁重的数据处理被推迟到软中断阶段执行。网卡收到包的场景里,硬中断把队列里的描述符标好,然后在中断返回前触发 NET_RX 软中断,内核随后在合适的时机把包从驱动层一路送到协议栈。

软中断的"合适的时机"有几种:硬中断处理返回之前,系统调用返回用户态之前,idle 循环里面,以及当软中断积压过多时,让内核线程ksoftirqd/N兜底处理。这里有个非常实用的观察点:ksoftirqd/N每个 CPU 一个,如果你发现某个核对应的ksoftirqd线程 CPU 占用明显变高,说明这个核的软中断已经处理不过来了,需要后台线程介入排空积压,这是问题已经从"偶发"变成"持续过载"的强烈信号。

2.2 不同软中断类型与内核参数

/proc/softirqs展示了系统里全部软中断类型的累计计数,常见的类型和触发场景可以按下面这张表理解:

类型全称通常触发场景
HI高优先级 tasklet少数驱动的紧急延后任务
TIMER内核定时器定时器超时处理、周期性调度
NET_TX网络发送报文发送收尾、发送队列处理
NET_RX网络接收收包处理,本文案例的主角
BLOCK块设备IO 完成回调
IRQ_POLL中断轮询驱动主动轮询模式
TASKLET常规 tasklet驱动的延后处理
SCHED调度器负载均衡、实时任务相关
HRTIMER高精度定时器高精度计时到期处理
RCURCU 回调RCU 维护、内存回收相关

排查网络型问题,目光要放在 NET_RX 和 NET_TX 上。但千万别养成本能反应,一看到单核软中断高就认定是网卡。TIMER 和 RCU 同样能制造出完全相同的表象。我印象很深的一个案例是,某台机器核 0 软中断也到了 100%,同事上来就调网卡队列,怎么调都没用,最后看/proc/softirqs才发现暴涨的全是 TIMER,根本没有网络包参与。原来是某个业务线程的高频定时器全部被调度到了核 0,加上内核默认的定时器迁移机制不允许跨 CPU 迁移,导致定时器软中断全部堆在同一个核上。这种"方向错了再努力也白费"的案例,值得每一个排查的人写进自己的笔记里。

另外,内核有几个参数直接控制软中断的"单次处理量":net.core.netdev_budget默认 300,表示一次 NET_RX 软中断最多能处理多少个包;net.core.netdev_budget_usecs是单次处理的时间上限;net.core.dev_weight表示每个网络设备单轮最多处理 64 个包。大流量场景下如果这些值太小,内核处理不完就会频繁唤起 ksoftirqd,表现就是软中断线程 CPU 持续偏高。不过这是后续细节,先把分布问题搞清楚更重要。

2.3 如何精准读取 /proc/softirqs 和 /proc/interrupts

/proc/softirqs这个文件天生就是"按 CPU 分列"的视图,每一行是一种软中断类型,每一列是一颗 CPU 的累计计数。只看一次输出没有任何意义,因为全是累计值,必须看两次采样之间的差量。我习惯这样操作:

cat /proc/softirqs sleep 5 cat /proc/softirqs

对比两次输出的增量,重点看 NET_RX 和 TIMER 分别在哪个 CPU 上增长最快。如果 5 秒内 NET_RX 在核 0 上涨了几十万甚至上百万,其它核几乎没有增量,问题已经从数据上被坐实了。

/proc/interrupts的读法也是同样的思路,但关注点不同。它展示的是硬件中断的分布,网络性能排查时要重点盯着网卡对应的那几个中断号。比如eth0相关的多个中断号,如果它们的计数几乎都集中在 CPU0 或者 CPU1,那软中断必然顺着硬中断的路径堆积在那几个核上。记住这个因果链:硬件中断落在哪个核,软中断默认就在哪个核处理。硬中断分配失衡,软中断必然失衡。

3. 排查链路:四步定位问题根因

3.1 第一步:先确认元凶是不是软中断

排查第一原则,永远是让数据替你做判断,而不是凭感觉。第一步的任务就一个:确认这个"单核高"到底是不是软中断引起的。命令非常简单:

# 按 CPU 维度持续采样,观察每颗核的 softint 占比 mpstat -P ALL 1 100

看输出里每颗核的%softint列。我的经验阈值:单核%softint持续高于 30%~40%,同时其它核几乎为 0,并且%idle还很充裕,基本就可以判定是软中断分配失衡。如果所有核的%softint都很高,那是整机流量过大或者驱动处理能力见顶的问题,和本文讨论的"单核失衡"不是一回事,修法也完全不同。

这里有一个容易被新手忽略的细节:mpstat -P ALL的输出字段很多,有人会看错列,把%irq和%soft弄混。%irq是硬中断时间占比,%soft才是软中断时间占比。两者都可以高,但对应完全不同的优化方向。本文案例里,%soft才是我们需要盯住的核心指标。

3.2 第二步:区分网络软中断和非网络软中断

确认是软中断后,第二步是看类型。把/proc/softirqs两次采样的增量按类型拆开,增量主要集中在 NET_RX/NET_TX,说明是网络收发包路径的问题;如果增量集中在 TIMER、RCU、SCHED,那就要往内核定时器、RCU 回调和调度器方向想,去调网卡纯属浪费时间。

这个判断步骤的价值在于"纠偏"。现实中我看到过不少案例:值班同事一看到核 0 的si满了,脑子里的第一反应就是"网卡队列有问题",然后 ethtool 一通操作,性能没半点改善,白白浪费了一顿饭的时间。等真正翻开/proc/softirqs,所有增量都写在 TIMER 那行,才恍然大悟。所以在动手改任何配置之前,花 30 秒确认一下软中断类型,是整个排查过程中性价比最高的一步。

如果是非网络类型,下一步排查方向也要分:TIMER 高要看是不是有高频定时任务的线程被绑核了、检查timer_migration参数;RCU 高要关注 CPU 隔离配置、内核版本和 RCU 回调堆积情况。这些场景和网络软中断的解法完全不同,别拿网络排查的思路硬套。

3.3 第三步:核查网卡队列与中断分布

确定是网络软中断之后,进入真正的硬件能力核查。这一步要回答三个问题:网卡支持多少个队列、当前启用了多少个队列、这些队列对应断言的中断都落在了哪些 CPU 上。

# 查看网卡队列能力与当前生效值 ethtool -l eth0 # 查看中断在 CPU 上的分布 cat /proc/interrupts | grep -E "eth0" # 查看各队列收包计数,判断流量是否均匀 ethtool -S eth0 | grep -E "rx-" | head -60

ethtool -l输出中有两行关键信息:Pre-set maximums是硬件和驱动能力的上限,Current hardware settings是当前真正生效的队列数。典型失衡场景就是 16 核机器,网卡当前只开了 2 个 Combined 队列,两个中断源天然决定了软中断只能在两个核上处理,其它核永远分不到活。

ethtool -S的逐队列计数用来判断"流量是否扎堆"。如果一个网卡开了 8 个队列,但某个队列的收包计数占到了总量的 80%,那说明哈希分配不均匀,要看 RSS 哈希配置、五元组分布特征,以及内核版本里哈希算法的表现。多队列并不是开了就完事,哈希均匀性本身也是一项需要验证的指标。

3.4 第四步:用 perf 做热点确认

前三步已经把范围圈定得差不多了,最后再用perf给排查结论上道保险。命令行可以这样用:

# 持续观察指定核上的热点函数 perf top -C 0

如果核 0 的栈顶频繁出现net_rx_action、process_backlog、tcp_v4_rcv、ip_rcv这类网络收包处理函数,说明软中断的时间确实大量消耗在网络路径上,推断成立。如果热点是rcu_core、run_timer_softirq、hrtimer_interrupt这类函数,那就回到第二步重新归类,别继续在网卡方向浪费时间。

perf这一步更大的价值体现在修复之后。修复前和修复后各 perf 一次,对比热点在哪些核上分布,是最直观的"验证有效"证据。我习惯把两轮的截图留下来,作为变更记录的一部分,后续复盘比翻聊天记录靠谱得多。

4. 常见根因归类:流量为什么偏偏堆在单核上

4.1 网卡队列数不足与驱动限制

网络收包的硬中断来自网卡接收队列,每个队列对应一个中断向量。CPU 很多、队列很少的情况下,队列到 CPU 的映射就变成了硬瓶颈。这是最朴素也最常见的一类根因。

实际运维中我遇到过两种变体。第一种是驱动和固件支持多队列,但当前配置只开了 1 个或者 2 个队列,比如通过ethtool -L扩一下就能解决。第二种是驱动本身只支持单队列,多见于一些老设备或者低端虚拟网卡,硬件层面没有扩展空间,只能靠后面要讲的 RPS 软件分发来补救。判断属于哪一种,ethtool -l的Pre-set maximums一目了然。

另外要提一个容易忽略的现象:有些网卡驱动通过配置文件或固件参数限制了队列数,改ethtool -L会报错或者静默无效。遇到这种情况要先查驱动模块参数,看看是否有max_channels之类的约束,别硬掰。

4.2 RPS/RFS 未启用时内核的默认行为

很多人以为"网卡开了多队列,流量就会自动均匀分布在多核上",这是对 RSS(Receive Side Scaling)机制最大的误解。硬件哈希把流量均匀分配到多个队列,这只是第一步;第二步,每个队列的中断被路由到哪个 CPU,由中断亲和性决定。如果亲和性没有配好,8 个队列的中断有可能全部落在 CPU0 和 CPU1 上,软中断自然也跟着堆在这两颗核上。

RPS(Receive Packet Steering)和 RFS(Receive Flow Steering)是内核提供的软件分发层。RPS 在软中断处理后,根据数据包哈希把包重新投递到其它 CPU 的 backlog 队列,让别的核也能参与协议栈处理;RFS 在 RPS 的基础上更进一步,把包投递到正在运行该连接对应进程的 CPU 上,从而改善缓存命中率。在绝大多数发行版里,RPS/RFS 并不是默认全量启用的,或者只对部分接口启用。这就解释了为什么很多机器明明是多队列、大内存、高主频,网络流量一上来,单核软中断照样打满。

4.3 中断亲和性与 irqbalance 的博弈

很多 Linux 环境默认开着 irqbalance 守护进程。它的角色是间歇性地重新计算并调整中断到 CPU 的映射,目标是让负载尽量分散。听起来很智能,但实际效果并不总是如人所愿,尤其是在有高性能网卡的机器上。

有两个很常见的坑。第一,irqbalance 基于自己的策略定期重排,如果你手动改了/proc/irq/N/smp_affinity,过几分钟可能就被它覆盖回去,改动"无效"。第二,某些网卡使用 MSI-X 多向量中断,irqbalance 对这类中断的权重判断通常不够仔细,它可能认为某些向量"负载很低"而把它们全部集中到同一个核上,制造出我们最不想看到的单核热点。另外,如果内核启动参数里配置了isolcpus或者通过tuned隔离了部分 CPU,irqbalance 默认不会把中断分配到那些隔离核上,负载就全压到剩余核里面。

所以排查时先确认 irqbalance 的状态很重要:

systemctl status irqbalance

在运行,不代表没有失衡问题;没运行,也不代表均衡就正确。要的是把它和手动配置之间的"权责关系"搞清楚,后面修复阶段再决定统一交给谁。

4.4 虚拟化环境中的半虚拟化网卡问题

容器和虚拟化场景里,单核软中断高几乎成了日常问题。虚拟机里看到的网卡,本质是宿主机虚拟出来的设备,如果这个虚拟设备默认只给一个队列,那虚拟机内部所有收包硬中断都会集中落在 vCPU0 上,单核直接拉满。宿主机上有多少物理队列反而不是关键,关键是虚拟队列数量与 vCPU 数量的匹配关系。

还有一层更隐蔽的情况:宿主机侧如果没有做好 vCPU 绑核或者物理核隔离,虚拟机的多个 vCPU 可能被映射到同一个物理核心上。从虚拟机内部看,是"vCPU0 软中断满了";从宿主机看,可能是"某个物理核被多个 vCPU 争抢打爆"。虚拟机内部调 RPS、调亲和性,可能一点效果都没有,因为瓶颈根本不在虚拟机这一层。排查这类问题不能只看一层,至少要同时拉出宿主机和虚拟机的 CPU 数据对照分析。

5. 解决方案落地:从队列扩展到 RPS/RFS 绑核

5.1 先判断属于"队列层"还是"分发层"问题

修复之前,先做一个简单的分类判断,确定问题属于哪个层面,再选择对应的方案。我的经验是先按下面这张表做个映射:

现象所属层面首选方案
网卡当前队列数远小于核数,硬件还支持扩展队列层ethtool -L 扩队列 + 中断绑核
网卡硬件只支持 1~2 个队列队列层启用 RPS/RFS 软件分发
队列数足够,但中断全部堆在前几个核分发层设置 smp_affinity 或启用 irqbalance
虚拟机内部虚拟队列少队列层平台侧扩队列 + 虚拟机内 RPS

这一步非常关键。我见过太多人把"开启 RPS"当成万能钥匙,结果在有大量物理队列、RSS 正常的机器上反而把性能改差了。软件分发是有额外开销的,硬件多队列已经能做好的事情,尽量不要让软件再重复做一遍。反过来,硬件队列确实不足时,RPS 又几乎是唯一高效的补救路径,该上还得上。

5.2 实操:网卡队列扩容与中断绑核

确认硬件支持多队列之后,扩队列的操作很简单,但后面的绑核步骤更容易被漏掉。完整流程是这样:

# 1. 查看当前最大支持数 ethtool -l eth0 # 2. 按需扩展,比如扩到 8 个 Combined 队列 ethtool -L eth0 combined 8 # 3. 扩完之后中断号会变化,重新查看 cat /proc/interrupts | grep eth0

第 3 步经常被人跳过,这是导致"扩了队列还是单核高"的最大原因。ethtool -L生效后,中断向量会重新分配,如果不重新配置亲和性,内核默认行为可能把所有新中断又堆回 CPU0,结果就是队列从 2 个变成 8 个,单核软中断依旧 100%。

绑核的写法,我建议直接用smp_affinity_list,因为它接受 CPU 编号列表,比算十六进制掩码直观得多:

# 把 eth0 相关的中断按顺序轮流绑定到 0~7 号核 for irq in $(grep -E "eth0" /proc/interrupts | awk -F: '{print $1}'); do cpu=$((irq % 8)) echo $cpu > /proc/irq/$irq/smp_affinity_list done

这只是一个最朴素的轮流分配脚本,真实环境里要根据中断数量和各核负载做更精细的规划。但核心思想是固定的:让每个队列的中断尽量落在不同的 CPU 上,并且要避开已经承载了重要业务线程的核,给它们留出余量。

5.3 实操:启用 RPS/RFS 均衡软中断

当硬件队列不足、扩无可扩时,RPS 是性价比最高的补救方案。对每个接收队列设置允许参与分发的 CPU 掩码:

# 以 16 核机器为例,掩码 ffff 代表允许 CPU0~CPU15 参与 echo "ffff" > /sys/class/net/eth0/queues/rx-0/rps_cpus echo "ffff" > /sys/class/net/eth0/queues/rx-1/rps_cpus echo "ffff" > /sys/class/net/eth0/queues/rx-2/rps_cpus # 网卡有几个 rx 队列就设置几个

rps_cpus的格式是 CPU 掩码的十六进制,bit0 对应 CPU0。比如01表示只允许 CPU0,ff表示 CPU0~CPU7,ffff表示 CPU0~CPU15。这个掩码要覆盖足够多的核,但也不是越多越好,理想情况是覆盖所有参与业务处理的核,同时排除掉那些被隔离给特殊用途的核。

如果还想启用 RFS 拿到缓存局部性收益,需要同时配置两个参数:

# 全局 flow 表大小,必须是 2 的幂 sysctl -w net.core.rps_sock_flow_entries=32768 # 每个队列允许跟踪的 flow 数,同样按 2 的幂设置 echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt echo 4096 > /sys/class/net/eth0/queues/rx-1/rps_flow_cnt

这里有两个实战经验。第一,rps_sock_flow_entries和rps_flow_cnt如果配置得太小,哈希冲突会非常严重,导致 flow 跟踪频繁失效,RFS 反而成为一个不稳定因素。第二,RPS 的本质是把包从一个核的软中断路径投递到另一个核的 backlog 队列,这天然带来跨核唤醒和缓存抖动的额外开销。低流量场景下不值得开,高流量但队列不足时才是它的主场。判断开不开的标准只有一个:改造前后对比实测收益,而不是"听说可以开就开"。

5.4 实操:irqbalance 的正确使用方式

如果不打算手动管理中断,也可以让 irqbalance 来统一调度,但一定要把策略说清楚。irqbalance 提供了一个配置入口,可以指定哪些中断参与均衡、哪些中断排除在外。实际部署中,我是这样做的:把高性能网卡的中断加入管理范围,同时把隔离核从它的调度范围内排除,避免它把中断塞进不应该被打扰的核。

反过来,如果选择手动管理,就要彻底停掉 irqbalance,不要留下一个"半自动"的混乱状态:

systemctl stop irqbalance systemctl disable irqbalance

停掉之后,上一节提到的绑核脚本要放进一个能开机自启的位置,比如系统服务单元或者启动脚本里,保证重启后配置依然生效。选择哪条路取决于环境规模:机器少、网卡多、队列规则复杂多变,手动绑核更可控;几十台同质化机器,irqbalance 统一管理更省心,但一定要定期抽验它的实际分布效果,纸面上的稳定不能替代运行数据的验证。

6. 验证与避坑:改完之后别急着宣布修复

6.1 验证指标与复查流程

修复完成不等于收工。一次没有验证的变更,本质上就是一次新的故障隐患。我的验证流程固定为四步:

  1. 重新跑mpstat -P ALL 1,确认原来 100% 的核%softint明显下降,其它核开始均匀分担;
  2. 对比/proc/softirqs两次差值,确认 NET_RX 增量不再集中在单核;
  3. 用压测工具或真实流量观察接口延迟,p99 是否回到正常水位;
  4. 用perf top -C N抽查几个关键核,确认网络收包热点确实被分散开了。

第 3 步往往是最有说服力的。曾经有一台机器,扩队列加绑核之后从数据上看分布已经非常均匀了,但业务 p99 反而比修复前更高。后来发现是绑核时把网卡中断放到了和业务进程同一个核上,虽然软中断分散了,但业务线程被挤得厉害,整体体验更差。这说明验证必须同时看"系统侧指标"和"业务侧指标",单看任何一边都可能被带偏。

6.2 我踩过的三个坑

第一个坑是扩队列之后忘了重新绑核。ethtool -L一旦生效,中断号会重新分配,所有手工设置的smp_affinity会失效。如果只扩队列不绑核,内核的默认行为很可能把所有新中断又堆回 CPU0,你会看到一个比原来更尴尬的局面:队列多了,单核软中断还是满的。现在我改造网卡时有一条铁律:扩队列和中断绑核必须同步完成,中间不留下任何"让内核自由发挥"的空档。

第二个坑是手动绑核和 irqbalance 互相打架。有一次我改完smp_affinity_list,验证时发现分布很快又乱了,排查了半天才发现 irqbalance 还在后台运行,它每隔一段时间重新计算一次中断分布,顺手覆盖了我的配置。从那以后我就坚持一个原则:要么全自动交给 irqbalance,要么全手动停掉它,不存在"我改几个你管其余"的中间状态,那只是给自己埋雷。

第三个坑是 RPS 在低负载场景下开反而负优化。一台流量不大但偶发脉冲的机器,我图省事直接配了全核 RPS,结果每个包都多了一次跨核投递和后端唤醒,延迟不降反升,p99 比没开之前更差。后来把rps_cpus收敛到物理核集合,排除超线程兄弟核,效果才恢复正常。RPS 这类软件分发机制的适用场景是"队列少、负载重",不是所有单核高都适合上它,改造之前做一次有数据的收益评估是必要的。

6.3 后续监控与兜底建议

最后说说长期维护。单核软中断高这类问题,最有价值的做法是在监控面板里加入"软中断/硬中断按核分布"的指标,让数据在告警触发前就替你盯着,而不是每次靠告警之后再上去抓现场。我自己的日常维护习惯是:

  • 给每台关键机器建立网卡中断基线,记录每个中断号对应哪个核,作为变更前快照;
  • 虚拟化环境里同时看宿主机侧的物理核占用,避免只盯着虚拟机内部数据做判断;
  • 定期归档/proc/softirqs的差量数据,流量上涨或者业务变更后,可以快速对比出是"分发失衡"还是"整体增长"。

这些工作听起来繁琐,但一旦形成常态,再遇到"单核软中断高"的告警,整个排查链路就会从撞运气变成按图纸施工。我个人在排过几轮这类问题之后,最大的体会就是:任何修复手段都先想清楚它作用于哪一层,再动手。网卡队列层的问题别拿软件参数硬凑,软件分发层的问题也别指望扩队列能解决,把根因确认这一步做扎实,后面的每一步都能走得很稳。

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

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

立即咨询