ThinkPHP 的 failed_jobs 表由框架自动写入,当队列任务抛出未捕获异常且启用 failed.driver=database 时触发;需确保异常未被 try/catch 吞掉、表结构匹配版本、正确配置 tries 和 retry_after,并通过 php think queue:failed 等命令查错与重试。

ThinkPHP 的 failed_jobs 表是怎么被写入的
失败任务写入 failed_jobs 表,不是靠手动调用,而是由框架在队列执行抛出未捕获异常时自动触发。前提是:你用了 think-queue 扩展(官方推荐),且驱动是 database 或 redis(Redis 需配合 failed 存储驱动)。
常见错误现象:failed_jobs 表空空如也,但明明任务报错了——大概率是没启用失败日志记录,或异常被 try/catch 吞掉没往上抛。
- 确认配置项
'failed' => ['driver' => 'database']已在config/queue.php中启用 -
database驱动下,failed_jobs表结构必须与 ThinkPHP 版本匹配(5.1 用failed_jobs,6.x 默认仍是该表名,但字段略有差异,比如fail_time在 6.1+ 改为failed_at) - 别在任务 handle 方法里无脑
try/catch+return,这会让框架认为“执行成功”,跳过失败记录逻辑
retry_after 和 tries 到底控制什么
这两个参数决定重试行为,但作用对象不同:tries 是“最多执行几次”,retry_after 是“单次执行后,隔多久再重试”。它们不冲突,但容易配错导致重试失效或堆积。
使用场景:网络请求类任务(如调第三方 API)适合设 tries => 3;耗时长的导出任务可能需要调大 retry_after,避免被队列进程误判为“卡死”而重复投递。
立即学习“PHP免费学习笔记(深入)”;
-
tries写在任务类的属性里:protected $tries = 3;,不是全局配置 -
retry_after是队列连接层配置,写在config/queue.php对应 driver 下,单位是秒(例如'retry_after' => 90) - 若
retry_after小于任务实际执行时间,任务还没结束就被标记为失败,反复重试——这是最常踩的坑
如何查失败任务并手动重试
查失败任务不能只看 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 - 注意:
retry不会清空原记录,只是把任务重新推入队列;重试成功后需手动php think queue:flush清理表
监控失败任务的关键指标和埋点位置
真正在生产环境做监控,光看表或命令不够。核心是抓三个信号:失败率突增、单任务反复失败、失败集中在某类任务。
最容易被忽略的是「失败原因分类」——ThinkPHP 默认不区分是代码异常、数据库超时还是 Redis 连接断开,全塞进同一个 exception 字段。想精准告警,得在任务基类里加一层包装:
- 重写任务的
fire或handle方法,在catch块中判断get_class($e),打上error_type标签(如'db_timeout'、'http_503')再抛出 - 用定时脚本查
failed_jobs表最近 5 分钟记录,按error_type聚合计数,超阈值就发钉钉/邮件 - 别依赖
failed_jobs.created_at做实时性判断——它写入时间可能比实际失败晚几百毫秒,建议用日志时间戳对齐
复杂点在于异常类型识别要覆盖框架底层(比如 think-queue 自己抛的 JobFailedException)和业务层,漏一种就可能让监控失灵。


















