一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

面试锦囊:Swoole 面试中关于 CPU 跑满的排查

时间:2026-07-21 09:37:00 编辑:袖梨 来源:一聚教程网

Swoole Server CPU跑满主因是onReceive等回调中混用同步I/O或协程未正确启用。需检查是否误用file_get_contents等阻塞函数、enable_coroutine=false导致调度混乱、onWorkerStart耗时初始化、CoMySQL超时未设、opcache被频繁reload清空,以及隐蔽的系统调用阻塞。

为什么 swoole_server 启动后 CPU 瞬间跑满?

不是配置错,也不是代码写死循环——大概率是 onReceive 回调里用了阻塞操作,或者协程未正确启动。Swoole 默认以协程模式运行,但如果你在 onReceive 里直接调用 file_get_contentscurl_execsleep 这类同步函数,当前协程会被卡住,而主 Reactor 线程仍在不断派发新连接/数据,最终积压 + 多路复用轮询开销把 CPU 推满。

实操建议:

  • 检查所有回调函数(尤其是 onReceiveonRequest)是否混用同步 I/O,替换成 co::readFileCoHttpClientco::sleep
  • 确认是否误开了 enable_coroutine => false,该配置会让 Swoole 回退到纯异步回调模型,但开发者仍按协程写法用 yield,导致调度混乱和 CPU 暴涨
  • strace -p $(pgrep -f 'php your_server.php') -e trace=epoll_wait,read,write 观察是否有高频 epoll_wait 返回 0 或频繁短读,这是事件循环空转的典型信号

top 显示 PHP 进程 CPU 高,但 gdb 堆栈全是 swWorker_onStart

这说明工作进程卡在启动阶段,常见于 onWorkerStart 里做了耗时初始化,比如连 MySQL、加载大文件、执行 shell_exec('composer dump-autoload')。Swoole 工作进程一旦卡住,就不会进入事件循环,Reactor 仍持续投递请求过来,造成“伪高负载”——实际没干活,只是反复失败重试。

实操建议:

  • 把所有初始化逻辑移到 onWorkerStart 外部,或改用 go(function () { ... }) 异步加载(注意:不能在 onWorkerStart 内直接 go,需先 Co::set(['hook_flags' => SWOOLE_HOOK_ALL])
  • 检查 max_request 是否设得太小(如 1),导致进程刚启动就重启,形成“启动→卡住→退出→重启”死循环
  • swoole_tableRedis 替代全局变量缓存,避免 onWorkerStart 中反复 require 大文件

用了 CoMySQL 还是 CPU 满?查 SHOW PROCESSLIST 发现大量 Sleep 状态

这不是数据库问题,是协程 MySQL 客户端没设置超时,或连接池没配好。当某个查询慢了、网络抖动、或服务端主动断连,CoMySQL->query() 会一直等响应,协程不 yield,整个 worker 就卡住。同时新请求继续进来,更多协程排队等待,CPU 在调度这些“假活跃”协程上白耗资源。

实操建议:

  • 必须设置 connect_timeoutread_timeout,例如:['timeout' => 0.5, 'read_timeout' => 1.0]
  • 不要手动 new 多个 CoMySQL 实例,改用 SwooleCoroutinePool 管理连接,避免连接数爆炸和复用失效
  • onWorkerStart 中预热连接池,而不是首次请求才建连;否则首请求必然触发连接建立+认证+慢查询三重延迟

为什么加了 opcache.enable_cli=1,CPU 还是下不来?

CLI 模式下 opcache 确实生效,但 Swoole 的 reload 机制(如 kill -USR1)会清空 opcache 缓存,如果频繁 reload,等于每次都在解释执行。更隐蔽的是:PHP 8.1+ 默认开启 opcache.jit_buffer_size,但 JIT 在协程密集场景可能引发指令缓存争用,反而拖慢调度。

实操建议:

  • 生产环境禁用 reload,改用平滑重启(kill -TERM + 新进程 warmup 后 kill -QUIT 旧进程)
  • 若用 PHP 8.1+,尝试关闭 JIT:opcache.jit=0,观察 perf top -p $(pgrep -f 'php server.php')zend_jit_unprotect 是否消失
  • 检查是否在协程中动态 evalcreate_function,这类操作无法被 opcache 缓存,且每次生成新函数对象,GC 压力大

真正难排查的,往往是那些“看起来合理”的阻塞点:比如日志组件里悄悄调了 flock,配置中心 SDK 用了 stream_socket_client 同步连接,甚至 date_default_timezone_set 在某些 glibc 版本下都有微秒级锁竞争。别只盯着大块 I/O,先 strace 看系统调用分布,再 perf record -g 抽样热点函数,比猜快得多。

热门栏目