Worker数量应设为物理核心数,而非逻辑核数;过多会导致上下文切换激增、缓存失效、性能下降;需配合worker_cpu_affinity auto和accept_mutex off(或reuseport on)优化。

Worker 数量设得太多,不是让系统跑得更快,而是让 CPU 不停地“换人干活”,结果谁都干不长、谁也干不好。核心问题在于:调度器被迫在大量 Worker 之间反复切换上下文,每个时间片极短,缓存频繁失效,寄存器反复保存恢复——实际算力没提升,开销反而暴涨。
按物理核心数设上限,别被逻辑核带偏
现代 CPU 的逻辑核(如超线程)不等于真实计算单元。设 32 个 worker 在 16 核 32 线程机器上,等于硬塞两拨人挤同一张办公桌——争抢 L1 缓存、执行单元和睿频资源。结果是部分核心过热降频,其余闲置,整体吞吐不升反降。
- 查真实物理核心数:
lscpu | awk '/Core\(s\) per socket:/ {cores=$4} /Socket\(s\):/ {sockets=$2} END {print cores * sockets}' - worker_processes 直接设为该数值,不乘以线程数
- 搭配
worker_cpu_affinity auto,Nginx 1.9.10+ 会自动跳过逻辑核,只绑物理核
关掉 accept_mutex,把连接分发从用户态搬走
旧版 Nginx 或手动开启 accept_mutex 后,多个 worker 会争一把全局锁来调用 accept()。在高并发下,这把锁就成了“瓶颈收费站”——CPU 频繁唤醒、排队、挂起,时间片全耗在等锁上,而不是干活。
- Nginx ≥ 1.11.3 默认已关闭,无需操作
- 低于该版本,必须在 events 块中显式写
accept_mutex off; - 同时启用
reuseport on;,让内核在 socket 层就哈希分发新连接,彻底绕过用户态锁和唤醒竞争
用固定 worker 池替代“每请求一进程/协程”模式
无论是 Nginx 的 worker、Go 的 goroutine,还是 Java 线程池,泛滥创建都会触发 CFS 调度器的自适应机制:任务越多,时间片越短,切换越密。这不是配置错了,是模型错了。
- HTTP 服务中,避免为每个请求启新进程或 goroutine;改用固定数量的长期 worker 消费任务队列
- Go 场景下,设
GOMAXPROCS与容器 CPU limit 对齐,并用带缓冲 channel 控制投递节奏 - Java/Celery 等场景,线程/worker 数建议 ≤ CPU 核心数 × 2,再结合压测确定拐点(例如 8 核机器试 12~16)
观察关键指标,别只盯 CPU 使用率
CPU 利用率 100% 不代表高效,可能是“忙得没空干活”。真正要盯的是调度是否健康:
-
vmstat 1看 cs(每秒上下文切换数):持续 > 10000 是明显过载信号 -
mpstat -P ALL 1看各核 %usr/%sys 是否严重不均,若某核 %sys 特别高,大概率在疯狂调度 -
perf stat -e context-switches,cpu-cycles,instructions对比优化前后,看 context-switches 是否下降 30%+,instructions/cycle 是否回升










