failed_jobs表对Redis驱动无效,必须手动监听JobFailed事件记录错误;retry_after须显式设为大于任务最大耗时(如90或120),否则未完成任务会被误判失败而循环重入。

failed_jobs 表对 Redis 驱动根本无效
很多人在 config/queue.php 里配了 failed → type => 'database',就以为 Redis 队列失败也能自动写进 failed_jobs 表。事实是:Redis 驱动压根不触发该逻辑,failed 配置只对 database 和 sync 驱动生效。
现象就是:php think queue:failed 命令永远查不到 Redis 失败任务,日志里也看不到任何错误堆栈。
- 必须手动监听
JobFailed事件,在app/event.php或服务提供者中注册回调 - 回调里至少记录
$event->job->getName()、$event->exception->getMessage()和$event->exception->getTraceAsString() - 别只打到
Log::error(),生产环境建议同步写入数据库或转发到 Sentry
retry_after 设太小会导致任务“假失败”循环重入
retry_after 不是重试间隔,而是单个任务允许执行的最长时间。它直接决定 Redis 驱动何时把还在跑的任务标记为“超时”,并重新入队。
比如发邮件任务实际耗时 85 秒,但 connections.redis.retry_after 是默认的 60,那第 61 秒时框架就会把它当失败处理,立刻重推——你看到的是“重复执行”,其实是“还没跑完就被砍了”。
立即学习“PHP免费学习笔记(深入)”;
- 该值必须显式配置,不能依赖默认值
- 建议设为预估最大耗时 × 1.5,例如 90 或 120,并上线后用监控(如 Prometheus + 自定义指标)验证真实执行时长
- 如果任务本身有外部依赖(如第三方 API 超时),要把这些延迟也包进去
--tries 参数不能替代异常捕获和重试策略
用 php think queue:work redis --tries=3 --delay=10 确实能让框架自动重试三次,但它只是“兜底”,不是“可靠处理”。
问题在于:每次重试都从头执行整个任务逻辑,没做幂等控制;且三次失败后任务就丢了,连错误上下文都不留。
- 真正要做的,是在任务类的
handle()方法里主动 try/catch,对可恢复错误(如网络抖动)做局部重试,对不可恢复错误(如参数缺失)立刻 throw 终止 - 关键数据操作前加唯一标识(如订单号+任务ID哈希),避免重复扣款、重复发券
-
--tries只适合兜住偶发性瞬时故障,不能用来掩盖设计缺陷
supervisor 配置漏掉 stopwaitsecs 会导致任务被暴力 kill
用 supervisor 管理 worker 进程时,如果只写了 autorestart=true 却没设 stopwaitsecs,supervisor 默认等 10 秒就发 SIGKILL 强杀进程。
而 ThinkPHP 的 Redis 驱动在退出前需要完成当前任务的 ACK(即从队列中移除),暴力 kill 会让任务卡在 redis:default 列表里不动,下次启动又消费一遍。
- 务必设置
stopwaitsecs=30(至少大于最长任务耗时) - 配合
startsecs=0避免 supervisor 把刚启动就退出的进程误判为崩溃 - 在任务类里加
register_shutdown_function做最后兜底:进程被中断时尝试记录当前状态到 Redis Hash



















