ThinkPHP队列任务反复重试的根源在于retry_after小于实际耗时、$tries未正确定义或未启用失败存储;需设retry_after≥任务最长耗时×1.5、声明public $tries、启用database失败驱动并用Supervisor替代--daemon。

ThinkPHP 队列任务执行失败后反复重试,本质是框架在检测到任务“超时”或“抛出异常”后自动触发重入机制,但若配置不当或异常未被正确处理,就会陷入无限循环——比如任务实际耗时 80 秒,retry_after 却设为 60,任务还没执行完就被标记失败,立刻重推,再执行、再失败……这不是代码写错了,而是关键参数和运行逻辑没对齐。
检查 retry_after 是否小于任务真实耗时
这是最常踩的坑。retry_after 不是重试间隔,而是单次任务允许执行的**最长秒数**。它由 Redis 驱动用来判断“这个任务是不是卡死了”。
- 打开
config/queue.php,定位到connections.redis.retry_after,确认值明确写出(如'retry_after' => 120),不要依赖默认 60 - 预估你任务中最长可能耗时(含网络请求、文件读写、数据库批量操作),设为该值 × 1.5;例如导出 Excel 耗时约 90 秒,建议设为 135
- 上线后结合日志观察真实执行时间:在任务
handle()开头和结尾打点记录时间戳,验证是否真超时
确认 $tries 属性是否正确定义且生效
重试次数由任务类自身的 $tries 属性控制,不是全局配置,也不是命令行参数能随意覆盖的。
- 在任务类中必须声明为 public 属性:
public $tries = 3;(private或protected无效) - 启动消费者时,别加
--tries=1这类参数,它会强制覆盖类里的设置;应直接用php think queue:work redis - 若没定义
$tries,ThinkPHP 默认值是 0 —— 表示无限重试,极易引发雪崩
确保失败能被记录和观测
Redis 驱动默认不存失败任务,意味着你根本看不到哪条失败了、为什么失败。不记录 = 不可控。
立即学习“PHP免费学习笔记(深入)”;
- 在
config/queue.php中启用失败存储:'failed' => ['driver' => 'database'],并确保failed_jobs表存在且字段兼容当前 ThinkPHP 版本 - 任务中避免无脑
try/catch吞掉异常:若捕获后只return或静默处理,框架认为“执行成功”,不会进 failed 表 - 更稳妥的做法是在
handle()外层加try/catch,手动记录错误日志:Log::error('Job failed', ['job' => get_class($this), 'exception' => $e->getMessage()]);
用 Supervisor 管理进程,禁用 --daemon
手动运行 php think queue:work --daemon 看似省事,实则埋雷:长期运行导致内存泄漏、MySQL 连接断开后无法恢复、异常后进程不退出。
- 卸载
--daemon,改用 Supervisor 守护:每次任务执行完进程自然退出,Supervisor 负责拉起新实例,天然释放内存和连接 - Supervisor 配置中务必设
autorestart=true和startsecs=0,避免因任务短时退出被误判为崩溃 - 同时开启数据库断线重连:
break_reconnect => true和break_match_str包含'2006 MySQL server has gone away'



















