Swoole\Server::stats() 是轻量实时监控入口,应低频异步调用(如每5秒或HTTP接口),返回connection_num等快照值;协程卡顿需结合Co::listCoroutines()分析;DB耗时监控推荐onTask/onFinish打点;进程崩溃须通过onWorkerError/onWorkerStop捕获并告警。

不用装额外扩展,Swoole\Server::stats() 就是离你最近、最轻量、最可靠的实时性能监控入口。
怎么快速拿到连接数和请求量?用 stats() 别写错时机
很多人在 onRequest 里每秒调一次 $server->stats(),结果发现 QPS 越高,监控本身开销越大——这是典型误用。它不是采样接口,而是快照接口,应该按需、低频、异步拉取。
- 正确做法:用
swoole_timer_tick(5000, fn() => var_dump($server->stats()))每 5 秒打一次快照,或暴露为 HTTP 接口(如/debug/stats)供 Prometheus 主动抓取 - 注意:
stats()返回的是整型数值,不含时间戳,你要自己补;worker 进程重启后计数清零,不能直接算累计值 - 关键字段:
connection_num(当前活跃连接)、accept_count(总接入数)、close_count(总关闭数)、tasking_num(正在执行的 task 数)
为什么 stats() 看不到协程卡顿?得靠 Coroutine::listCoroutines()
stats() 告诉你“有多少人在线”,但不告诉你“谁在发呆”。协程堆积、阻塞 I/O、死循环协程,都会让 connection_num 正常但响应变慢——这时候必须看协程堆栈。
- 加个调试端点:
Co::listCoroutines()返回所有协程 ID,再对每个 ID 调用Co::getBackTrace($cid)查堆栈,就能定位卡在哪一行 - 别在生产环境高频调用
getBackTrace,它会暂停协程并采集上下文,单次耗时可能达毫秒级;建议只在报警触发后手动触发一次 - 常见陷阱:用
sleep()或usleep()阻塞协程——Swoole 不会自动协程化它们,会导致整个 worker 卡住
数据库慢查没日志?用 onTask + onFinish 打点比中间件更稳
ORM 层埋点容易漏掉原生 SQL 或连接池超时,而 Swoole 的 onTask/onFinish 是进程级钩子,只要任务走 Task 进程,就逃不掉监控。
- 把耗时 DB 操作封装进
task(),在onTask开始记时间,在onFinish结束算耗时、记录 SQL 片段(注意脱敏)、状态码 - 不要依赖 MySQL 的 slow log:Swoole 连接池复用连接,slow log 只反映 MySQL 侧耗时,不包含连接获取、协程调度、序列化等全链路时间
- 警惕
task_worker_num设太小:如果 task 队列持续 > 0,说明任务积压,这时tasking_num会涨,但stats()不会告诉你哪个 SQL 在拖后腿
想长期监控却漏掉进程崩溃?onWorkerError 和 onWorkerStop 必须写进日志
很多服务跑着跑着就少一个 worker,QPS 下降但 stats() 看不出异常——因为崩溃后进程被 master 自动拉起,stats() 计数重置,旧错误彻底消失。
- 在
onWorkerError里记录$worker_id、$worker_pid、$exit_code、$signal,这是定位 coredump 或 OOM 的唯一线索 -
onWorkerStop要区分是正常退出(比如 reload)还是异常退出($worker->status !== SWOOLE_WORKER_EXIT),后者必须告警 - 别只写文件日志:Swoole 进程崩溃时,file_put_contents 可能失败;至少同步发一条 UDP 到本地 syslog 或 Loki
真正难的不是看到数字,而是理解数字背后协程、进程、系统调用之间的耦合关系。比如 connection_num 稳定但 accept_count 突增,大概率是客户端频繁建连断连;tasking_num 长期 > 0,但 task_worker_num 已满,就得查是不是某个 task 里用了 sleep 或没 await 异步操作——这些细节,stats() 不说,但你得会问。











