Workerman 4.0.20 进程 CPU 持续飙高是用户态过载所致,需通过 top、pidstat 确认 %usr >75%,再用 perf record/report 和火焰图定位高频 PHP 函数,结合 strace 检查事件循环空转、Timer::count() 监控定时器泄漏、lsof 分析阻塞 I/O,并通过内存峰值日志验证 GC 压力。

Workerman 4.0.20 进程 CPU 持续飙高至 90% 以上,top 中可见某 Worker 子进程 %CPU 长期稳定在 85–100%,响应延迟同步上升,但连接数和请求量未明显增加,说明不是正常负载所致,必须立即定位热点代码或配置缺陷。
确认是否为用户态 CPU 过高
执行 top -p $(pgrep -f "php.*start.php" | head -1),按 Shift+H 切换线程视图,观察单个线程的 CPU 占用。若主线程(TID 与 PID 相同)持续 >80%,说明是 PHP 业务逻辑或扩展调用导致;若多个线程均匀占用且 sys% 较高,则可能是内核态问题。
运行 pidstat -u 1 -p $(pgrep -f "php.*start.php" | head -1) 5,重点看 %usr 列。若该值长期 >75%,确认为用户态过载,进入下一步;若 %sys >30%,需检查系统调用频次(如 epoll_wait 轮询、sendfile 大量小包)。
定位热点函数与调用栈
对目标 Worker 进程 PID 执行:perf record -g -p PID -a -- sleep 10,随后运行 perf report -n --sort comm,dso,symbol。
重点关注符号列中出现频率最高的 PHP 函数名(如 MyService::handleMessage、json_encode、preg_match),特别是嵌套深度 ≥4 的调用链。若看到大量 gc_collect_cycles 或 zend_mm_alloc_small,说明 GC 压力过大,大概率由频繁对象创建或未释放引用触发。
【不要跳过 perf script 导出环节】:执行 perf script > perf.out 后用 FlameGraph/stackcollapse-perf.pl perf.out | FlameGraph/flamegraph.pl > flame.svg 生成火焰图——视觉上最宽的顶部区块即为真实瓶颈,比文本报告更直观。
检查定时器与事件循环异常
方法一:strace 实时观测事件循环
执行 strace -tt -e trace=epoll_pwait,epoll_ctl,timerfd_settime -p PID 2>&1 | grep -E "(epoll_pwait|timerfd)"。若每秒输出 ≥50 行 epoll_pwait(..., 1000) = 0,说明事件循环空转——Worker 无实际任务却高频轮询,常见于 onMessage 回调中误写死循环、或 Timer::add() 创建后未 del() 导致定时器堆积。
方法二:检查定时器注册总量
在 Worker 启动脚本中插入临时监控:Worker::$onWorkerStart = function() { \Swoole\Timer::tick(3000, function() { echo "Active timers: " . \Workerman\Timer::count() . "\n"; }); };
若该数值在 10 分钟内从 5 涨到 200+,说明存在高频创建未销毁的定时器。
【Timer::count() 返回值包含已到期但回调尚未执行的定时器】,这类“僵尸定时器”仍占事件循环资源,必须结合 onMessage/onClose 清理逻辑排查。
验证是否存在同步阻塞调用
第一步:检查代码中是否直接使用了 file_get_contents、curl_exec、mysqli_query 等同步阻塞函数。
第二步:用 lsof -p PID | grep -E "(REG|sock)" 查看进程打开的文件描述符类型。若出现大量 REG 类型(普通文件)且路径含日志文件、配置文件,说明可能在 onMessage 中反复读取大文件;若出现 sock 但状态为 canread 长期不变化,说明 socket I/O 卡住。
第三步:强制触发一次堆栈 dump:kill -SIGUSR2 PID(需 Workerman 启动时启用 Worker::$daemonize = false 或调试模式),查看 workerman.log 中最后几行是否有 in /path/to/your/code.php on line X 的阻塞位置记录。
快速验证 GC 与内存压力关联
在 onMessage 回调入口处添加两行:
$before = memory_get_peak_usage(true);gc_collect_cycles();
在回调出口处添加:
$after = memory_get_peak_usage(true);if ($after - $before > 2 * 1024 * 1024) { file_put_contents('/tmp/big-alloc.log', date('Y-m-d H:i:s') . " +".($after-$before)."B\n", FILE_APPEND); }
运行 5 分钟后检查 /tmp/big-alloc.log。若每秒均有记录,说明单次消息处理分配了超 2MB 内存,极可能触发频繁 GC,进而拉升 CPU。此时应审查是否在循环中 new 大对象、是否用 json_decode($json, true) 解析超大 JSON 导致数组复制膨胀。

















