核心问题是消费后未调用$job->delete()导致消息重复执行,须在fire()中用try-catch-finally确保删除或调用$job->fail(),并配置maxattempts和failed存储为redis以实现失败可追溯、堆积不卡死。

TP6 消息队列消费报错,核心问题往往不在“消息没收到”,而在于“收到后没正确收尾”或“出错后没闭环处理”。修复关键不是重试更多次,而是让每次消费有始有终、失败可追溯、堆积不卡死。
确认消费端是否真正删除了消息
ThinkPHP 的 Redis 队列驱动不会自动删除已消费任务。只要 $job->delete() 没被调用,这条消息就一直留在 queues:default 列表里,下次轮询还会取出来执行——表面是重复消费,实质是从未完成。
- 检查每个 Job 类的
fire()方法末尾是否明确写了$job->delete(); - 如果中间抛异常(如数据库写入失败、HTTP 请求超时),
delete()就不会执行,必须用 try-catch 包裹并确保 finally 删除,或改用$job->fail()显式标记失败 - 别依赖“执行完就自动删”,TP6 Redis 驱动没有这个机制
限制重试次数,避免无限循环卡死
默认配置下,失败任务会不断重试,尤其在逻辑错误未修复时,可能把整个队列拖慢甚至阻塞。
一款AI工具,主要用于管理 OpenClaw 所使用的来自 OpenRouter 的免费 AI 模型。自动按质量对模型进行排序,配置回退机制以应对速率限制,并更新 opencla...,适合需要提升相关任务效率的用户。
- 在
config/queue.php中设置全局重试上限:'retry_after' => 90(单位秒),防止长任务被误判失败 - 为具体 Job 类定义
public $maxAttempts = 3;,超过即进入失败队列 - 避免在
fire()内部手动 sleep + retry,这会阻塞当前进程;应交给队列系统统一调度
失败任务必须落地且可监控
1 万并发下,failed_jobs 数据表容易成为瓶颈。直接写 DB 不仅慢,还可能因锁表影响主业务。
- 将失败存储改为 Redis:
'failed' => ['type' => 'redis'],利用 zset 按时间排序,读写更快 - 每个失败任务应包含原始数据、错误堆栈、发生时间,便于快速定位是网络、DB 还是业务逻辑问题
- 搭配定时任务(如每 5 分钟)执行
php think queue:flush清理已确认失败项,不要等人工介入
消费者进程本身要稳得住
报错常来自消费者挂了、重启了、或被系统 kill,而不是消息内容有问题。
- 禁用
php think queue:listen单进程模式,改用 Supervisor 管理多组 worker,例如 5 组 × 10 进程 - 每个 worker 加参数:
--sleep=1 --max-jobs=1000,防空轮询和内存泄漏 - 若用 Swoole 协程监听,确保 Redis 连接复用、任务内不阻塞 IO;出错时用
continue而非break,避免整条协程中断










