Workerman本身不提供任务队列持久化能力,需解耦任务生成与消费并选用Redis或RabbitMQ等持久化存储;onMessage中禁止同步I/O,应仅轻量入队;Redis队列须BRPOP超时+连接保活;RabbitMQ须delivery_mode=2+手动ack+DLX;还需幂等校验与重试边界控制。

Workerman 本身不提供内置的“任务队列持久化”能力,它只是一个常驻内存、事件驱动的 PHP 应用容器。大并发下数据丢失或排队失败,根本原因往往不是 Workerman 不行,而是你没把「任务生成」和「任务消费」这两层真正解耦,也没选对底层存储。
onMessage 里直接写数据库 = 自己造雪崩
常见错误是:用户一发请求,onMessage 回调里立刻 INSERT INTO orders 或调用 file_put_contents,还带 sleep(1) 或未设超时的 curl_exec。这会导致:
- 事件循环被阻塞,后续连接无法及时响应
- 进程卡死,新连接被系统丢弃(
Connection refused或超时) - 数据看似“提交了”,实则事务未提交、日志没刷盘、MySQL 连接池耗尽
正确的做法是:只做最轻量的事——把任务塞进一个能落盘的队列。
- 使用
Redis的LPUSH+BRPOP(推荐中小规模) - 或
RabbitMQ的basic_publish+ 持久化队列 +ack模式(中大型、强一致性场景) - 绝对避免在
onMessage中执行任何同步 I/O、慢 SQL、阻塞网络调用
Redis List 做队列必须开 BRPOP 超时 + 心跳保活
很多人用 Redis 做队列,但忘了两个关键点:
-
BRPOP默认无限等待,Worker 进程可能因网络抖动假死却无感知 -
Redis连接空闲超时(默认 300 秒),连接断开后不重连,后续任务全积压
建议这样写消费者逻辑:
-
BRPOP queue_name 5:最多等 5 秒,超时就检查自身状态(如内存、信号)再继续循环 - 在
onWorkerStart初始化连接,并用try/catch包裹每次BRPOP,捕获RedisException后重建连接 - 每次成功取到任务后,立即
DEL或SET一个带 TTL 的标记键,用于监控积压量
不要依赖 phpredis 的自动重连——它不保证命令重试,也不处理连接中断期间的数据一致性。
RabbitMQ 场景下必须配 delivery_mode=2 和手动 ack
如果你选 RabbitMQ,光连上还不够。默认消息是内存存储,服务重启就丢;默认消费者是自动 ack,消息一推过去就算成功,哪怕 Worker 进程当场崩溃。
必须显式设置:
- 生产端:投递时指定
['delivery_mode' => 2],确保消息写入磁盘 - 消费端:关闭自动
ack($channel->basic_qos(0, 1, false)+$channel->basic_consume(..., false)),并在业务逻辑执行完、数据真正落库后再调用$channel->basic_ack($delivery_info['delivery_tag']) - 死信交换机(DLX)要提前绑定,让处理失败三次的消息进
dlx.order.failed队列,而不是直接丢弃
漏掉其中任意一项,高并发下都可能出现“消息已发送,但数据库没写,且再也找不回来”的情况。
真正难的不是写 Worker,而是设计好「任务唯一性校验 + 幂等入口 + 失败重试边界」。比如订单号作为消息 body 的 key,消费前先查库是否已存在;重试最多 3 次,第 4 次进死信人工介入。这些逻辑不在 Workerman 文档里,但在生产环境一天都绕不开。

















