ThinkPHP队列任务失败多因进程状态、异常捕获或配置覆盖,而非代码逻辑;需分四类排查:未执行(查进程与启动参数)、不重试(验maxTries/public/驱动/命令覆盖)、无失败记录(防异常被try-catch吞掉)、查失败任务(用failed-show/retry命令而非直查表)。

ThinkPHP队列任务执行失败,多数情况不是代码逻辑出错,而是进程管理、异常传播或配置错位导致的“静默失败”。关键要分清:是根本没执行?执行了但没重试?还是失败了却没记录?下面从四个实际场景切入,讲清楚怎么定位、怎么修、怎么防。
任务压根没被执行?先看消费者进程是否真在跑
常见现象:推送任务后,日志里完全没痕迹,failed_jobs表也空。这不是任务坏了,很可能是消费者没启动或已僵死。
- 用
ps aux | grep "queue:work"确认进程是否存在;有残留进程要先kill干净再重启 - 别用
--daemon——它会让 PHP 进程长期不释放内存,几小时后容易 OOM 崩溃;改用supervisor或systemd托管 - 启动时必须指定队列名:
php think queue:work --queue=default,否则默认只监听default,其他队列的任务会被忽略 - MySQL 空闲超时(默认 8 小时)会导致连接断开,后续任务执行时报
MySQL server has gone away却不报错;建议加--sleep=3让进程定期休眠,或在连接层配置ping保活
任务执行了但只试一次就进 failed 表?检查重试配置三要素
ThinkPHP 不自动重试,必须三个条件同时满足:任务类声明public $maxTries、驱动支持重试(Redis/database)、启动命令没用--tries=1覆盖。
-
$maxTries必须是public属性,写成private或protected无效;也不能放在config/queue.php里,那里没有这个配置项 - 命令行参数优先级高于类属性,比如
php think queue:work --tries=1会强制只试 1 次,哪怕类里写了public $maxTries = 3 -
sync驱动不支持重试,哪怕设了$maxTries也没用;只有redis和database驱动才维护attempts计数 - 用
php think queue:work --once测试时,无论$maxTries多大,都只执行 1 次就退出,不触发重试逻辑
失败了却没进 failed_jobs 表?重点查异常是否被吞掉
failed_jobs 表写入靠框架捕获未处理异常。一旦你在handle()里加了try/catch又没重新抛出,框架就认为“执行成功”,直接跳过失败记录。
立即学习“PHP免费学习笔记(深入)”;
- 确保异常没被静默吃掉:不要在
handle()最外层写try/catch后仅return或Log::info,至少要throw $e或调用$job->fail($e) - Redis 驱动默认不写
failed_jobs表,必须显式配置'failed' => ['driver' => 'database'],否则任务耗尽重试后直接丢弃,连错误都看不到 - 检查
runtime/log/目录是否可写,且config/app.php中'log' => ['level' => ['error']]已开启错误日志 - Linux 下注意类文件路径和命名空间严格匹配,比如
app\job\SendSms必须对应app/job/SendSms.php,大小写错一个字母就加载失败
查失败任务和手动重试?别只盯数据库,用命令更高效
failed_jobs表里存的是序列化数据,exception字段还是 base64 编码的,直接查很费劲。生产环境应结合命令行工具快速定位。
- 列出所有失败任务:
php think queue:failed,显示 ID、连接、队列、失败时间 - 查看某次失败的原始异常:
php think queue:failed-show 123(123 是 ID),能直接看到堆栈和报错行 - 重试单个任务:
php think queue:retry 123;重试全部:php think queue:retry all - 重试成功后,记得清理表:
php think queue:flush,否则failed_jobs会越积越多 - 若要用自动重试代替人工干预,启动时加
--tries=3 --delay=10,让框架失败后延 10 秒再试,但注意这仍不会记录原始堆栈,需配合日志监控



















