workerman cpu 占用过高需分层排查:先定位热点进程,再依次检查事件循环阻塞、定时器滥用、内存泄漏、reuseport 配置及日志输出问题;对应优化异步调用、定时器管理、内存回收、端口复用和日志策略。

Workerman 5.2.3 服务 CPU 占用过高,不能只盯着“是不是代码写慢了”,得按层次拆解:是业务逻辑真忙?还是系统资源卡点?或是框架/PHP自身在空转? 大多数高 CPU 场景其实不是算力不够,而是某处阻塞、轮询、泄漏或配置失当导致进程反复做无用功。
? 先快速定位真实热点
打开终端,执行:
# 查看 Workerman 主进程及子进程的 CPU 占用(按 %CPU 排序)
ps aux --sort=-%cpu | grep -E "(workerman|start\.php)"
# 或更精准地只看当前项目相关 PHP 进程
pgrep -f "YourAppPath/start.php" | xargs -r ps -p {} -o pid,ppid,%cpu,%mem,comm,args --sort=-%cpu
如果发现某个 worker 进程 CPU 持续 >90%,立刻抓堆栈:
# 替换为实际 PID(比如 12345) gdb -p 12345 -ex "thread apply all bt" -ex "detach" -ex "quit" 2>/dev/null | grep -A 5 -B 5 "php"
⚠️ 注意:生产环境慎用
gdb;若无法安装,改用strace -p <pid> -c -s 100</pid>统计系统调用耗时,重点关注epoll_wait、write、sendto是否高频或超时。
? 常见原因与对应处理
1. 事件循环被阻塞(最典型)
- 表现:单个 worker CPU 飙高,其他 worker 闲置;
onMessage或onConnect回调里有同步 I/O(如file_get_contents、sleep()、未设超时的curl_exec)。 - 解决:
- 所有网络请求必须用 异步客户端(如
workerman/http-client、workerman/mysql); - 文件读写改用
eventfd+stream_select或移交到异步任务队列(如Worker::sendToWorkerProcess()); - 删除所有
usleep()、sleep(),改用Timer::add()定时触发。
- 所有网络请求必须用 异步客户端(如
2. 心跳或定时器滥用
- 表现:
Timer::add()被高频注册(如每毫秒一次),或回调中未unset()导致定时器堆积。 - 解决:
- 检查所有
Timer::add()调用,确保:- 间隔 ≥ 100ms(心跳建议 30s+);
- 使用
Timer::del($timer_id)显式销毁不再需要的定时器; - 避免在
onMessage中重复添加同一逻辑的定时器。
- 检查所有
3. 内存泄漏引发 GC 频繁触发
- 表现:CPU 高的同时,内存持续缓慢上涨;
top看%MEM也偏高;jstat类工具不适用,但可用 PHP 自带函数验证:// 在 onWorkerStart 或定时器中加 echo "Memory: " . memory_get_usage() . " / " . memory_get_peak_usage() . "\n";
- 解决:
- 启用
Worker::$max_requests = 1000(处理 1000 次请求后自动重启 worker); - 检查是否滥用
static或$GLOBALS缓存大对象(如未清理的 session 数据、未释放的 PDOStatement); - 使用
xdebug_debug_zval()或gc_collect_cycles()辅助排查循环引用。
- 启用
4. reusePort 关闭 + 连接分配不均
- 表现:部分 worker CPU 100%,其余很低;
netstat -an | awk '/:2345/ && $6=="ESTABLISHED" {print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr显示连接集中在少数 IP(即少数 worker)。 - 解决:
$worker = new Worker('tcp://0.0.0.0:2345'); $worker->reusePort = true; // 必须开启 $worker->count = min(24, (int)shell_exec('nproc')); // 建议 ≤24,避免调度开销
5. 日志或调试输出未关闭
- 表现:开发环境上线后仍保留
var_dump()、echo、file_put_contents(..., FILE_APPEND)到慢速磁盘。 - 解决:
- 生产环境禁用所有
error_log()、var_dump(); - 日志改用异步方式(如
Worker::$stdoutFile = '/dev/null'+ 第三方异步 logger); - 禁用
display_errors = Off和log_errors = On(错误日志走 syslog 或专用通道)。
- 生产环境禁用所有
? 补充建议:监控与预防
-
加一行状态检查(放在
onWorkerStart):\Workerman\Worker::$pidFile = __DIR__ . '/workerman.pid'; \Workerman\Worker::$statusFile = __DIR__ . '/workerman.status';
然后用
php start.php status实时看各 worker 的连接数、请求数、内存。 -
启用 OPcache 并确认生效:
php -i | grep opcache.enable # 应为 `opcache.enable => On`
-
TCP 参数微调(需 root):
echo 'net.core.somaxconn = 65535' >> /etc/sysctl.conf echo 'net.ipv4.tcp_tw_reuse = 1' >> /etc/sysctl.conf sysctl -p
不复杂但容易忽略。











