发布时间:2026-09-23 04:31 更新时间:2026-09-23 04:31 阅读量:0
很多站长第一次遇到 CPU 软中断问题时,场景都差不多:top 里 %Cpu(s) 的 si 一栏长期在 20% 以上,us 和 sy 都不高,业务却开始变慢,ping 的延迟抖动、Nginx 日志里出现偶发超时。更迷惑的是,整机 CPU 总使用率看着不满,但某个核已经被打满。这种情况通常和网络收包路径有关——数据包从网卡到内核协议栈的处理,很多环节都跑在软中断上下文里。
软中断(softirq)不是传统意义上由进程调度的任务,它由内核在中断退出后、或由 ksoftirqd 内核线程择机执行。网卡每收一个包,硬中断先通知 CPU,接着把收包工作交给 NET_RX_SOFTIRQ 软中断去走协议栈。如果所有包都涌向同一个 CPU,这一个核的 si 就会飙高,其余核闲着。所以排查方向很明确:先确认软中断压在哪个核上,再让收包工作分摊到多个核。
先用 top 按 1 展开每个核,观察 si 是集中在某个 CPU 还是普遍偏高。然后看网卡中断统计,/proc/interrupts 里每个中断号后面的冒号就是该中断在各 CPU 上的计数,如果某一列数字远大于其他列,说明硬中断集中在这颗核。
# 展开每核查看 si,按 1 切换显示
top
查看网卡各队列中断在各 CPU 的分布
cat /proc/interrupts | grep -i eth0
查看软中断整体统计与每 CPU 情况
cat /proc/softirqs | grep -i NET_RX
如果中断计数集中在 CPU0,同时该核 si 明显偏高,基本可以判断是中断亲和性没有铺开。注意:不同发行版与网卡驱动对队列命名不同,队列名可能是 eth0-TxRx-0 这类形式,具体以实际环境的 /proc/interrupts 输出为准。
现代物理网卡大多支持多队列(RSS),一块网卡可以暴露多个接收队列,每个队列绑定不同 CPU,硬件层就把流量分开了。先用 ethtool 看队列数和当前组合情况。
# 查看网卡支持的通道数与当前配置
ethtool -l eth0
查看网卡特性,确认是否开启多队列相关 offload
ethtool -k eth0 | grep -i -E 'rx|queue|rss'
临时把接收队列调整为 4(需网卡与驱动支持)
ethtool -L eth0 combined 4
调整队列后,中断号会相应增加。接着可以把中断绑定到指定 CPU,写入 /proc/irq/<中断号>/smp_affinity_list 即可。绑定的原则是让每个队列落到不同核上,避开处理关键业务的那几个核。irqbalance 服务会自动做这件事,但它有时会把所有中断丢到 CPU0,所以要么配置 irqbalance 的忽略列表,要么在确定场景下手动绑定。
# 假设 24、25、26、27 是 eth0 的四个队列中断号
for i in 24 25 26 27; do
echo $((i-24+2)) > /proc/irq/$i/smp_affinity_list
done
查看绑定结果
cat /proc/irq/24/smp_affinity_list
上面的写法把四个队列分别绑到 CPU2 到 CPU5,只是示例,实际用哪个核要结合业务进程分布来定。写入 /proc 的配置重启即失效,需要持久化时可通过 systemd 单元或 rc.local 在启动阶段执行,具体方式以发行版文档为准。
如果网卡队列数少于 CPU 核数,或者用的是虚拟网卡(很多云主机的 virtio 网卡队列有限),硬件层分摊不开,就要靠 RPS(Receive Packet Steering)。RPS 在软件层根据数据包的哈希值,把收包软中断分派到不同 CPU,相当于用 CPU 换队列数。
开启 RPS 是往 /sys/class/net/<网卡>/queues/rx-<n>/rps_cpus 写一个十六进制掩码。掩码的每一位对应一个 CPU,比如 CPU0 到 CPU3 对应掩码的 0 到 3 位,写 f 就表示这四颗核都参与。
# 查看当前 RPS 配置(0 表示未开启)
cat /sys/class/net/eth0/queues/rx-0/rps_cpus
让 CPU0-3 参与 RPS,掩码为 f
for q in /sys/class/net/eth0/queues/rx-*; do
echo f > $q/rps_cpus
done
调整 RPS 流表大小,默认 4096,可按需增大
echo 32768 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
RPS 生效后,/proc/softirqs 里 NET_RX 在各 CPU 上的计数会明显均衡,si 也会从单核转移到多核。需要注意的是,RPS 只影响接收方向,发送方向对应的是 XPS;掩码写错(例如写了不存在的 CPU 位)不会报错但也不生效,改完记得回读确认。
收包路径上还有几个内核参数会影响软中断压力。netdev_max_backlog 是每个 CPU 的收包队列长度,小包高并发场景下队列容易满并丢包,日志里会出现 dropped;somaxconn 影响 accept 队列;tcp_max_syn_backlog 影响半连接队列。这些值不是越大越好,盲目调大只是把丢包点后移,要结合 ss -lnt 的 Send-Q 和 Recv-Q 观察。
# 查看当前值与丢包统计
sysctl net.core.netdev_max_backlog
cat /proc/net/softnet_stat
临时调整(写入 /etc/sysctl.d/ 下文件可持久化)
sysctl -w net.core.netdev_max_backlog=8192
sysctl -w net.core.somaxconn=4096
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
使配置文件生效
sysctl --system
/proc/net/softnet_stat 每行对应一个 CPU,第一列是处理的包数,第二列是因队列满被丢弃的包数。如果某颗核的第二列持续增长,说明该核的 backlog 不够或负载不均,优先解决分布问题而不是一味加大数值。
最后提醒两点:一是调整队列、RPS 与中断绑定前,建议记录原始值,便于回退;二是云主机、容器环境下的网卡能力往往受限,虚拟网卡可能不支持多队列或 RPS 效果有限,实际可用能力以云厂商与内核版本为准。排查完这一轮,如果 si 仍然偏高,再往协议栈上层看,比如 conntrack 表满、iptables 规则过多导致每个包都要走一遍匹配,也会把软中断顶上去。
| 📑 | 📅 |
|---|---|
| Docker 容器启动即退出排查:exit code、logs 与前台进程 | 2026-09-23 |
| MySQL单表过亿后的分页优化:延迟关联与覆盖索引 | 2026-09-23 |
| Nginx 反代下 Cookie 域与 Path 错乱排查实战 | 2026-09-23 |
| Linux 改完 fstab 重启起不来:UUID 混用与救援恢复 | 2026-09-22 |
| MySQL备份文件损坏怎么验证:一致性校验与恢复演练 | 2026-09-22 |
| Nginx客户端与后端长连接谁在复用:端口耗尽排查顺序 | 2026-09-23 |
| Linux conntrack 表满导致丢包:现场定位与容量评估 | 2026-09-23 |
| rsyslog 与 journald 双写日志重复或丢失排查 | 2026-09-23 |
| PostgreSQL表膨胀与autovacuum不生效排查实战 | 2026-09-23 |
| Redis 主从切换后写入报 READONLY:三种拓扑的误配排查 | 2026-09-23 |