Workerman主动推送延迟主因是事件循环被同步操作阻塞、定时器滥用或连接状态管理失当;需排查同步IO、优化定时器调度、清理UID映射泄漏、监控发送缓冲区。

Workerman主动推送信息出现明显延迟,通常不是网络抖动或带宽不足导致,而是事件循环被阻塞、定时器误用或连接状态管理失当引发的连锁反应——你看到的“3秒后才收到消息”,背后可能是主进程正在执行一个未加异步处理的数据库查询,或者某个连接的onClose回调里残留了未清理的Timer ID。
检查事件循环是否被同步操作阻塞
打开你的onMessage或自定义推送逻辑,确认有没有直接调用file_get_contents、mysqli_query、curl_exec这类同步IO函数。这些操作会彻底挂起当前Worker进程的事件循环,所有其他连接的消息收发、心跳检测、定时器触发都会暂停,直到该操作完成。
改用workerman/mysql异步客户端或Swoole\Coroutine\Http\Client替代curl_exec;数据库查询必须走协程化驱动,否则哪怕只延迟100ms,高并发下也会形成排队雪崩。
这一步操作起来很简单,直接把文件拖进去就行。
排查高频定时器造成的调度挤压
方法一:检查是否为每个连接单独创建了心跳定时器
例如在onConnect中写Timer::add(30, function() { $connection->send('ping'); }),每新增一个用户就新建一个30秒定时器。当连接数达5000时,事件循环每秒要处理近170次定时器插入/删除,底层红黑树频繁重平衡,CPU花在调度上远多于发包。
方法二:改用单一定时器轮询+时间分桶
【必须将所有连接按last_ping_time放入SplMinHeap或按秒级分桶数组】,主定时器固定100ms执行一次,只扫描最近1秒内需心跳的连接。这样5000连接下,每次扫描最多几十个对象,开销下降两个数量级。
方法三:关闭无意义的定时器日志输出
如果Timer回调里有error_log()、file_put_contents()或var_dump(),IO阻塞会直接卡死整个Worker进程——这不是延迟,是停摆。
验证UID映射是否泄漏导致内存持续增长
第一步:在onClose回调中检查是否只unset($connections[$connection->id])而没清理$worker->uidConnections[$connection->uid]。
第二步:确认$worker->uidConnections是否被其他静态变量或闭包意外引用,导致PHP GC无法回收Connection对象。
第三步:运行php workerman.php status,观察RES列内存值是否随在线人数线性上升。若上升,说明Connection对象堆积,后续新连接的事件响应必然变慢。
这个泄漏不会立刻报错,但运行6小时后,原本2G内存的Worker可能涨到4.8G,然后开始频繁GC,每次gc_collect_cycles()耗时超200ms,所有推送延迟直接翻倍。
确认WebSocket帧发送是否被缓冲区阻塞
调用$connection->send($data)后,数据并非立即发出,而是先进入内核socket发送缓冲区。若客户端网络差或接收端处理慢,缓冲区填满后send()会阻塞(即使设置了非阻塞模式,PHP层仍会等待可写事件)。
解决办法:在$connection上启用send buffer监控,当buffer_size > 64 * 1024时,主动断开该连接或降级为轮询;不要依赖默认的send()返回值判断成功,要监听$connection->onBufferFull事件做熔断。

















