Workerman雪崩源于连接初始化位置错误及缺乏协同防护,须将Redis等连接移至onWorkerStart、客户端实施指数退避、服务端限流+连接池隔离,并强化心跳时效性检测。

Workerman 无法靠单点心跳或重连逻辑扛住雪崩式重连,必须结合客户端退避、服务端限流与连接池隔离三者协同防御。
为什么 onConnect 里直接 new Redis() 会雪崩失败?
客户端网络闪断后集体重试,所有 Worker 进程几乎同时在 onConnect 或业务入口中新建 Redis 连接。这时会出现:多个进程争抢有限的 Redis 连接数、TCP TIME_WAIT 耗尽本地端口、Redis 服务器因瞬时连接激增拒绝新连接(报错 ERR max number of clients reached)。
根本原因不是 Workerman 框架问题,而是把“连接初始化”错误地放在了请求入口路径上——它本该是进程级预热动作,却被当成了每次连接都要执行的操作。
- 必须把 Redis/MySQL 等连接全部移入
onWorkerStart,每个 Worker 进程只建一次连接 - 连接对象要绑定到 Worker 进程全局变量(如
$GLOBALS['redis']),而非$connection属性或局部变量 - 禁止在
onMessage或onConnect中调用new Redis()、new PDO()等构造函数
如何让客户端重连不扎堆?
客户端自身不做退避,服务端再强也扛不住。前端、App、IoT 设备都必须实现指数退避(exponential backoff),比如:第一次断开后等 1s 重连,失败则等 2s、4s、8s… 最大不超过 60s,并加入随机抖动(±30%)。
若你控制不了客户端(如第三方硬件设备),就得在网关层做兜底:
- 用 Nginx 在 upstream 前加
limit_conn和limit_req,按 IP 或 token 限频 - 在 GatewayWorker 的
onConnect中检查客户端 IP 的 5 秒内连接次数,超阈值直接$connection->close() - 对高频重连 IP 记录到 Redis,后续连接直接拒绝或降级为 HTTP 短轮询
连接池不是可选插件,而是雪崩防护刚需
单个 Worker 进程复用一个 Redis 连接,在高并发下会成为瓶颈(串行等待),且一个连接异常会导致整个 Worker 后续所有请求失败。连接池能解耦“连接获取”和“业务处理”,提供熔断、超时、最大空闲数等关键能力。
推荐方案:
- PHP 侧用
co\Pool(Swoole 协程池)或spiral/roadrunner配合 Workerman 做进程外连接管理 - 更轻量的选择是
phpredis+ 自研简易池:预创建 N 个Redis实例存入数组,用spl_queue管理可用连接,获取时 pop,归还时 push - 务必设置
timeout和read_write_timeout,避免某个慢查询拖垮整个池
心跳检测不能只发 ping,还要验响应时效
很多项目只在 onWorkerStart 启动一个定时器,每 30 秒向所有连接发 ping,但没检查对方是否真的回了 pong 或响应是否超时。这会让“假死连接”长期滞留,占用资源,等雪崩来临时反而加剧压力。
正确做法:
- 给每个
$connection绑定last_pong_time时间戳,收到pong就刷新 - 心跳定时器中遍历连接,对
time() - $connection->last_pong_time > 90的连接主动close - 客户端发
ping后,服务端不立即回pong,而是记录ping_received_at;客户端超时未收到pong就主动断连重试
真正危险的从来不是连接断开本身,而是大量“半死不活”的连接卡在中间,既不通信也不释放——它们会在雪崩临界点前悄悄吃掉最后一丝资源余量。











