90%队列任务失败源于进程状态、异常捕获和配置覆盖问题,而非代码逻辑;常见表现为work进程卡住或只执行一次,实为MySQL断连或进程未重启导致。

队列任务失败,90% 问题出在进程状态、异常捕获和配置覆盖上,而不是代码逻辑本身。
php think queue:work 进程卡住或只执行一次就停
这是最常被误判为“任务失败”的现象。实际是 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计数 -
queue.php配置文件里加'maxTries' => 3是无效的,框架压根不读这个键
任务抛异常但没进 failed_jobs,或日志里看不到失败记录
说明异常没被框架捕获,或任务类根本没被正确加载。
立即学习“PHP免费学习笔记(深入)”;
- 确保任务类路径和命名空间完全匹配,比如类
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']]已开启错误级别 - 数据库驱动下,
failed_jobs表的attempts字段值必须和实际执行次数一致,如果不等,说明重试根本没发生,优先查$maxTries和启动参数
延迟任务和 retryAfter 混用导致重复入队
$retryAfter 控制的是失败后“隔多久再入队”,不是首次执行前的延迟 —— 和 delay() 完全不同语义,混用会引发严重问题。
-
$retryAfter = 5表示失败后等 5 秒再把任务推回队列;而$this->delay(5)是让任务首次执行延迟 5 秒,两者不能互相替代 - 如果在构造函数里写
$this->delay(5),又设了$retryAfter = 1,结果是:第一次延迟 5 秒执行 → 失败 → 立刻重试(因为retryAfter是 1 秒)→ 又失败 → 再立刻重试……形成高频重入 -
$retryAfter单位是秒,且只对database/redis驱动生效;设成0.1在高并发下极易拖垮数据库 - 真正需要延迟执行的任务,应该用
Queue::later(300, new SendSms(), $data),而不是靠$retryAfter
重试机制看似简单,但 $maxTries 的可见性、--tries 参数的覆盖优先级、work 进程的连接生命周期,这三个点只要漏掉一个,排查就会绕远路。



















