laravel队列任务失败后默认不重试,必须显式设置public $tries = n(n为大于0的整数)才触发重试;未定义、为null或0均导致失败直接写入failed_jobs表,且$backoff、retryuntil等仅在$tries有效时生效。

Laravel 队列任务失败后默认不重试,必须显式配置才生效;没设 $tries 就等于“只执行一次,失败即进 failed_jobs 表”,别指望它自动兜底。
怎么让任务失败后自动重试
重试不是默认行为,得靠 $tries 属性触发。它必须是整数且大于 0,null、0 或未定义都等同于“不重试”。
-
$tries必须声明为 public 属性,写在类体顶层,不能在构造函数或handle()里动态赋值——反序列化前框架就已读取该值 - 命令行启动时加
--tries=3是全局 fallback,会被任务类里的$tries覆盖 - 如果用了 Laravel 9+,可在分发时链式调用
dispatch(new Job)->tries(2),优先级介于命令行和类属性之间 - 切记:超时(
--timeout或$timeout)触发的是进程强制终止,不进失败表、不重试、不回调failed()
怎么控制重试间隔不炸库
数据库驱动(database)不支持原生延迟,失败任务会立刻回到队列头,高频重试容易压垮 DB。必须用 $backoff 或 retryAfter() 控制节奏。
-
public $backoff = [1, 5, 15];:第 1 次失败后延 1 秒,第 2 次延 5 秒,第 3 次延 15 秒;数组长度要 ≥$tries,否则后续重试无延迟 -
public function retryAfter() { return $this->attempts * 10; }:比$backoff更灵活,支持运行时计算秒数 - 别在
database驱动下依赖--delay启动 worker,它对失败重试完全无效 - Redis 驱动支持毫秒级 delay,但需手动调用
dispatch()->delay(now()->addMilliseconds(100)),$backoff仍是秒级
retryUntil() 怎么用才不白写
retryUntil() 设的是绝对截止时间点,不是间隔;但它和 $tries 互斥——只要定义了 $tries,这个方法就直接被忽略。
- 返回值必须是
DateTimeInterface实例,例如now()->addMinutes(5);返回time() + 300或字符串会静默失效 - 适合时效敏感任务,比如支付确认必须在 15 秒内完成:
return now()->addSeconds(15); - 注意时区:如果服务器时间和应用时区不一致,
now()可能算错;建议显式用Carbon::now('UTC') - 该方法只在任务抛出未捕获异常时生效;
return false或静默失败不会触发重试
人工重试失败任务要注意什么
php artisan queue:retry 是唯一官方入口,但重建任务时会反序列化 failed_jobs.payload 字段——这里最容易翻车。
- 检查
payload是否被截断:MySQL 的TEXT类型可能存不下大对象,导致unserialize(): Error at offset - 任务里别引用不可序列化的资源,比如
$request、$_SERVER、cURL handler、闭包,重试时会直接报错 - 重试全部任务慎用
php artisan queue:retry all,可能引发重复扣款、重复通知等副作用 - Web 界面重试需自己实现控制器,必须加鉴权(如 IP 白名单或管理员 token),Horizon 和 Telescope 都不支持点击重试
最易被忽略的一点:retryUntil() 和 $tries 不能共存,而很多人先写了 $tries = 3,再补 retryUntil() 却发现根本没用——框架早按次数逻辑走完了。











