workerman主进程默认监控子进程异常退出并自动fork新进程补位,但对卡死无感知;可靠保活需在onworkerstart中用timer定时检查健康状态并主动exit触发重启。

Workerman 本身不提供自动故障检测与恢复能力,所谓“自愈”必须靠你主动设计心跳、状态上报、超时重试和进程重启策略;没有共享状态存储(如 Redis)或外部协调服务(如 Consul),单靠 Worker::reload() 或 kill -USR1 是无法实现节点级自愈的。
怎么让 Worker 进程崩溃后自动拉起?
Workerman 的主进程(Master)默认会监控子进程(Worker)退出,并在子进程异常退出时自动 fork 新进程补位。但这个机制只对「子进程意外退出」有效,对「子进程卡死但未退出」完全无效。
-
Worker::reload()不会触发自动拉起,它只是优雅地关闭旧子进程、启动新子进程,属于主动更新,不是故障恢复 - 如果子进程陷入死循环或阻塞在
sleep()/fgets()等同步调用中,Master 无法感知,也不会干预 - 真正可靠的保活方式是:在
onWorkerStart中启动一个独立的定时器(如Timer::add()),定期检查自身健康状态(例如写入时间戳到 Redis),超时未更新即主动exit(1)——这样 Master 才会拉起新进程 - 不要依赖系统级进程守护(如 systemd)来重启整个 Workerman 主进程,那会导致所有连接中断;应让 Master 自身完成子进程级恢复
怎么判断某个任务调度节点是否失联?
关键不是“连接是否还通”,而是“它是否还在持续上报心跳”。Workerman 没有内置心跳协议,必须你自己基于 TCP 连接 + 定时消息 + 共享存储来实现。
- 每个 Worker 节点(包括主调度节点和从执行节点)需在启动后向 Redis 写入带 TTL 的心跳键,例如
worker:192.168.1.10:8282,TTL 设为 10 秒 - 主调度节点应另起一个
Timer::add()定时任务,每 5 秒扫描 Redis 中所有worker:*键,对过期或缺失的节点标记为「失联」,并触发任务重分配 - 不能只靠
onClose回调——网络闪断、客户端静默断开、防火墙中断等场景下,onClose可能根本不会触发 - 避免用
ping或telnet做存活探测:TCP 连通 ≠ 业务可用,且轮询成本高、易被限频
任务执行失败后如何自动重试与转移?
Workerman 的 onMessage 是纯回调,不带重试语义。失败重试必须由你控制流程,且要区分「瞬时失败」和「永久失败」。
- 执行任务前,先将任务元数据(ID、参数、尝试次数、超时时间)存入 Redis List 或延时队列(如
redis->zAdd('delayed_tasks', time() + 30, $task_json)) - 任务执行成功后,用 Lua 脚本原子性地从队列中移除该任务;失败则根据错误类型决定:网络超时可 +1 尝试次数后重新入队,SQL 唯一冲突则直接丢弃
- 不要在
onMessage里直接sleep(1); $connection->send(...)做重试——这会阻塞整个连接,影响其他请求 - 跨节点转移任务时,务必带上原始节点 ID 和失败原因,否则无法做归因分析和熔断(比如某台机器磁盘满导致批量失败)
为什么用 Redis 而不是数据库存心跳和任务状态?
因为自愈逻辑对延迟极度敏感:心跳检测要毫秒级响应,任务重试要在秒级内完成,而 MySQL 单次 round-trip 通常在 5–50ms,Redis 在局域网内稳定在 0.1–0.5ms。
- MySQL 的行锁、事务开销、主从延迟都会拖慢故障判定速度,一次失联识别可能从 2 秒拉长到 15 秒以上
- Redis 的
EXPIRE+KEYS(不推荐)或SCAN+TTL组合足够支撑万级节点的心跳管理,且无连接数瓶颈 - 切记:Redis 也得高可用——单点 Redis 故障会导致整个自愈机制瘫痪,必须用 Redis Sentinel 或 Cluster
- 如果业务要求强一致性(如金融类任务),可在 Redis 做快速失败转移,再异步落库补全审计日志,不要把两者混在同一路径
真正的自愈难点不在代码怎么写,而在于「如何定义‘异常’」:是连接断了?是 5 秒没回包?是连续 3 次心跳超时?还是 CPU 持续 95% 超过 60 秒?这些阈值必须结合你的硬件、网络、任务类型反复压测才能定下来,调得太松会频繁误判,太紧又起不到保护作用。











