Swoole CPU 高主因是协程泄漏或事件循环阻塞,非计算密集型代码;需检查 Coroutine::count()(>2000 危险)、Timer::count()、避免同步调用、禁用高频日志、合理配置 worker_num 与 max_coroutine,并用 strace/gdb 定位 epoll_wait 或协程调度热点。

CPU 高不等于代码慢,而是协程调度或事件循环被卡住了——这点和 PHP-FPM 完全不同。
协程泄漏导致 CPU 持续 100%
PHP-FPM 下 CPU 高通常是因为某段计算密集型逻辑(比如大数组排序、正则回溯),但 Swoole 中更常见的是协程没挂起、没释放,让事件循环反复轮询空转。
- 用
Coroutine::count()查当前协程数,持续 > 2000 就危险;配合Swoole\Timer::count()看定时器是否堆积 - 检查所有
go(function () { ... })是否包含未 await 的协程调用,或漏掉co::sleep()/yield - 特别注意 WebSocket
onMessage回调里直接$server->push()而没做$server->exist($fd)判断,会触发 warning 并阻塞调度器 - 避免在协程中调用原生
sleep()、file_get_contents()、curl_exec()—— 这些在 Swoole 协程环境下会退化为同步阻塞,CPU 不降反升
日志与调试输出在 Swoole 里杀伤力翻倍
FPM 下每次请求打一条 var_dump() 只影响单个进程;Swoole 是常驻进程,高频日志会持续占用主线程 CPU 去格式化、写入、刷缓冲。
- 生产环境必须关掉
swoole.log_level(设为SWOOLE_LOG_WARNING或更低) - 禁用所有
echo/print_r()在onRequest、onReceive等回调中出现 - 若需临时调试,改用异步写日志:
swoole_async_write("debug.log", "msg\n", -1, function() {}) - 别用
Xdebug连接 Swoole 进程——它会强制关闭协程调度,CPU 直接飙到 100% 且无法响应
配置参数误设引发空转竞争
worker_num、reactor_num、max_coroutine 这几个值不是越大越好,设错反而让 CPU 在无意义的线程切换或协程争抢中耗尽。
立即学习“PHP免费学习笔记(深入)”;
-
worker_num建议设为 CPU 核心数 × 1~1.5,设成 64 这种远超物理核数的值,会导致大量上下文切换开销 -
max_coroutine默认是 3000,但如果业务平均协程生命周期 > 5s,且并发请求数高,就容易堆满后调度器疯狂扫描就绪队列 - 用
top -H -p $(pgrep -f "php your_server.php")查看具体线程 CPU 占比,若某个reactor线程长期 99%,说明网络事件分发不均或存在长连接异常 - 启用
enable_reuse_port => true可缓解多 Reactor 间负载不均,但仅限 Linux 3.9+ 内核
strace 和 gdb 在 Swoole 场景下要换用法
PHP-FPM 下 strace -p PID 看 read/write/accept 就够了;Swoole 进程里这些系统调用极少,真正热点在 epoll_wait 和协程切换路径上。
- 先跑
strace -p PID -e trace=epoll_wait,clone,sched_yield,观察是否频繁返回 timeout 或一直阻塞在epoll_wait - 若看到大量
sched_yield,基本确认是协程没让出控制权(比如死循环中没co::sleep(0)) - 用
gdb -p PID后执行source ~/.gdbinit-swoole(需提前装 swoole-gdb 插件),再swoole coroutines可直接打印所有协程堆栈 - 不要对 Worker 进程用
gdb attach超过 3 秒——可能触发心跳超时,被 Master 进程误判为崩溃并重启
最易被忽略的一点:Swoole 的 CPU 高往往不是某一行代码的问题,而是多个小问题叠加——比如一个没清理的定时器 + 一次未判断连接状态的 push + 一段高频 debug 日志,三者合起来就把调度器拖垮了。盯住 Coroutine::count() 和 memory_get_peak_usage() 这两个指标,比看 top 数字更有诊断价值。



















