ThinkPHP队列任务失败90%源于进程状态异常、异常未被捕获或配置被覆盖;常见表现为work进程卡住或只执行一次,实为MySQL断连或进程未重启所致。

ThinkPHP队列任务执行失败,90%不是代码写错了,而是进程状态异常、异常没被框架捕获、或配置被悄悄覆盖。真正卡住业务的,往往是那些“看起来在跑,其实早就不动了”的静默失败。
work进程卡住或只执行一次就停
这是最常被当成“任务失败”的假象。本质是 MySQL 连接复用导致的断连,或进程意外终止后没人重启。
- work 模式下数据库连接长期复用,若空闲超 8 小时(MySQL 默认 wait_timeout),下次执行就会报 MySQL server has gone away,但进程不退出,后续任务直接静默失败
- 用 php think queue:work --once 测试时,哪怕写了
$maxTries = 3,也只执行 1 次就退出,根本不会触发重试 - 改了任务类代码却没重启进程,旧进程还在跑老逻辑,日志里永远看不到新改动的效果
- 检查进程是否存活:
ps aux | grep "queue:work";确认无残留后,用php think queue:work --queue=default重新启动
任务进了 failed_jobs 却不重试,或只试 1 次
ThinkPHP 不自动重试,重试行为必须由 $maxTries 属性 + 驱动支持 + 启动参数三者共同满足,缺一不可。
-
$maxTries必须是 public 属性,写成private $maxTries = 3无效 - 命令行加了
--tries=1(如php think queue:work --tries=1)会强制覆盖类里的$maxTries -
sync 驱动不支持重试,哪怕设了
$maxTries也没用;只有database和redis驱动才真正维护 attempts 计数 -
config/queue.php里加'maxTries' => 3是无效的,框架压根不读这个键
抛了异常却没进 failed_jobs,日志也看不到记录
说明异常没被框架捕获,或者任务类根本没被正确加载。
- 确保任务类路径和命名空间完全匹配,比如类
app\job\SendSms必须放在app/job/SendSms.php,Linux 下大小写敏感 - 未捕获的异常会导致进程崩溃,但 work 模式下可能只打印错误不写日志 —— 在
handle()方法最外层加try-catch,并手动写日志:Log::error('SendSms failed', ['exception' => $e->getMessage()]); - 检查
runtime/log/是否可写;同时确认config/app.php中'log' => ['level' => ['error']]
Redis 队列“消息丢失”或重复执行
多数所谓“丢失”,其实是配置错误降级为 sync 模式,或消费端没调用 $job->delete()。
- 检查是否真正在用 Redis:运行
php think queue:listen --queue=default后,执行redis-cli KEYS "queues:*",没有queues:default就说明 fallback 到了 sync 模式 -
config/queue.php中default必须是小写'redis',connections.redis.type也必须是'redis',不能有空格或大小写混用 - Redis 驱动不会自动清理已消费任务 —— 所有成功路径结尾必须显式调用
$job->delete() - 失败时不要直接
return,要用$job->release($delay)延迟重试(如$job->release(60))
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











