必须用microtime(true)在onmessage入口和send前打点,按worker_id分桶统计耗时,避免file_put_contents同步写,结合status命令与strace交叉验证瓶颈进程。

用 microtime(true) 手动打点记录每个请求的耗时
Workerman 没有内置请求耗时直方图或 P95/P99 统计,最直接有效的方式是在 onMessage(或 onReceive)回调里用 microtime(true) 手动打点。注意必须在回调入口就记录开始时间,不能放在异步逻辑之后,否则会漏掉前置处理开销。
常见错误是只在业务逻辑块里计时,忽略了协议解析、反序列化、连接上下文初始化等环节——这些也属于“该 Worker 处理此请求的实际耗时”。
- 开始时间必须写在回调第一行:
$start = microtime(true); - 结束时间放在响应发送前(比如
$connection->send()之前) - 避免在协程中误用
time()或date(),精度不够,microtime(true)是唯一可靠选择 - 不要把耗时直接
echo或var_dump,会阻塞事件循环;应写入内存数组或异步日志
按 Worker 进程维度聚合耗时数据,而非全局平均
Workerman 是多进程模型,不同 Worker 进程可能负载不均、内存状态不同、甚至运行在不同 CPU 核心上。把所有请求耗时混在一起算平均值,会掩盖单个进程的毛刺或退化问题。
你应该为每个 worker_id 单独维护一组统计桶(例如每秒 100ms 区间计数),而不是只维护一个全局数组。这样后续用 php your_start.php status 查看时,才能关联到具体哪个 Worker 进程持续高延迟。
- 用
Worker::$pid或$worker->id作为 key 存储耗时样本 - 避免用
file_put_contents(..., FILE_APPEND)实时写文件——同步 IO 会拖慢整个进程 - 推荐用
pcntl_signal注册SIGUSR1信号,在信号处理器中 dump 当前统计快照到临时文件,再由外部脚本读取分析 - 如果用了 Task Worker,记得也要在
onTask里加同样逻辑,任务执行耗时和网络请求耗时要分开看
结合 status 命令输出交叉验证异常 Worker
php your_start_file.php status 输出里的 mem_usage 和 connections 是关键交叉指标。一个 Worker 耗时突增,往往伴随其内存占用缓慢上涨(比如未释放的闭包、缓存未清理、资源句柄泄漏),或连接数长期高位不降(说明连接没被正确 close,后续请求排队等待)。
不要只盯着 total_request 看吞吐,它掩盖了请求是否均匀分布。比如某个 Worker 的 connections 是其他进程的 3 倍,但 total_request 只高 10%,那大概率它正在处理大量长耗时请求,或者卡在某个同步调用上。
- 定期采样
status输出,对比各进程的mem_usage和connections波动趋势 - 若发现某 Worker
mem_usage持续爬升且耗时同步增长,优先检查该进程里是否用了static数组缓存、未 unset 的大对象、或未关闭的 Redis/MySQL 连接 - 用
strace -tt -p PID配合耗时峰值时刻抓取,确认是不是 epoll_pwait 返回异常频繁(空转)或卡在write/sendto上
别跳过 Task Worker 和自定义 Process 的耗时采集
很多团队只在主 Worker 的 onMessage 里埋点,结果线上出现整体延迟升高,排查半天才发现是 Task Worker 里某个数据库查询没走协程驱动,或者自定义 Process 中的 while(true) 循环休眠时间设成了 0。
Task Worker 的耗时对终端用户感知一样关键——比如推送服务里,Worker 收到消息后投递到 Task,Task 查库+组装模板+发短信,这一整条链路的延迟都算在用户等待时间内。
- 在
onTask回调开头和finish()前同样打点,用Worker::$taskWorkerCount区分是 Task 进程还是普通 Worker - 自定义
AbstractProcess类中,必须确保每个循环体都有usleep()或Coroutine::sleep(),且耗时采集要覆盖整个循环周期,不只是业务逻辑块 - 如果用了 GatewayWorker,别忘了在
BusinessWorker的onMessage里也加耗时统计——那里才是实际业务逻辑所在











