Workerman 进程数再多也扛不住软中断单核瓶颈,因网卡收包触发的 NET_RX 软中断若全集中在 CPU0,会导致 socket 缓冲区无法及时腾空,Workerman 的 onMessage 无法及时接收数据,引发连接堆积、延迟飙升和超时断连;根本原因是 Linux 默认未分散网卡中断亲和性,需手动将多队列中断绑定至不同 CPU 核并配合 Worker 进程数与 CPU 核数匹配、禁用 reusePort、调大内核参数等协同优化。

Workerman 本身不直接处理网卡中断(硬中断或软中断),那是 Linux 内核和驱动层的事。你看到 si%(softirq)在 top 里飙高、某个 CPU 核长期 100%、吞吐上不去,问题不在 Workerman 代码里,而在系统层面——网卡收包后触发的软中断(NET_RX)全压在一个 CPU 上,Workerman 进程再怎么多进程也抢不到空闲核去干活。
为什么 Workerman 进程数再多也扛不住软中断单核瓶颈
Workerman 的每个 Worker 进程依赖事件循环监听 socket 可读事件,但前提是数据得先从网卡进到内核协议栈、再拷贝到 socket 接收缓冲区。这个“进”的过程由软中断完成。如果 NET_RX 软中断只在 CPU0 上跑,那所有网卡收包都得排队等 CPU0 处理,socket 缓冲区迟迟不腾空,Workerman 的 onMessage 就收不到新数据,连接堆积、延迟飙升、甚至触发超时断连。
- 现象:
top中si%占比高(比如 >30%),且集中在单个 CPU;cat /proc/interrupts | grep eth显示某块网卡的中断号几乎全落在一个 CPU 上 - Workerman 表现:连接数上不去、
ESTABLISHED连接堆积、ss -s显示recv-q持续非零 - 根本原因:Linux 默认的中断亲和性(
/proc/irq/*/smp_affinity_list)没调,网卡中断没分散
如何把网卡软中断负载分摊到多个 CPU
核心是手动修改中断亲和性,让同一块网卡的多个 RX 队列中断绑定到不同 CPU。现代网卡(如 Intel ixgbe、i40e、mlx5)基本都支持多队列(RSS),每队列对应一个中断号。
- 确认网卡是否启用多队列:
ethtool -l eth0查看Combined最大值;ls /sys/class/net/eth0/device/msi_irqs/看是否有多个 IRQ 目录 - 查看当前中断分布:
cat /proc/interrupts | grep eth0,注意每行开头的 IRQ 编号和后面各 CPU 的计数 - 将 IRQ 绑定到 CPU 列表(例如 CPU0-3):
echo 0-3 > /proc/irq/<code>IRQ_NUM/smp_affinity_list(需 root) - 更稳妥的做法是用
irqbalance服务(确保已安装并启用),它会自动均衡;但生产环境建议关掉它,自己静态绑定,避免运行时抖动 - 验证效果:压测时再跑
watch -n1 'cat /proc/interrupts | grep eth0',观察各 CPU 计数是否趋近均匀
Workerman 配合软中断优化的关键配置点
光分散软中断不够,Workerman 进程也要跟上节奏,否则负载还是打不均。
-
worker_num建议设为 CPU 总核数(不是线程数),避免进程数过多导致调度开销反升;若已做中断绑定到 CPU0-3,则最多启 4 个 Worker 进程,并用taskset绑定到对应 CPU:taskset -c 0 php start.php start - 禁用
reusePort(Workerman 默认关闭),除非你明确需要;开启后多个 Worker 监听同一端口,反而可能加剧软中断竞争 - 确保
net.core.somaxconn和net.core.netdev_max_backlog足够大(如 65535),防止内核丢包导致重传、进一步加重软中断压力 - 不要在
onMessage里做阻塞操作(如同步 MySQL 查询),否则 Worker 进程卡住,即使软中断分摊了,业务层也撑不住
真正卡住高并发的,往往不是 PHP 代码写得不够好,而是网卡中断没散开、CPU 核被软中断独占。做完中断亲和性调整后,si% 下降、各 CPU 负载均衡、Workerman 的 onMessage 回调响应明显变快——这时候你才真正开始在用多核,而不是在给单核打工。

















