Laravel队列任务默认失败不重试,仅当配置--tries参数或$tries属性时才自动重试;$backoff定义可变退避时间,retryUntil设定绝对截止时间;重试前需确保payload可反序列化;高优任务应隔离队列并独立监听。

重试不是自动发生的,得靠配置或命令触发
默认情况下,Laravel 队列任务失败后**不会自动重试**,而是直接写入 failed_jobs 表并停止。所谓“自动重试”,实际只发生在两种场景:一是启动 worker 时显式传了 --tries 参数;二是任务类里定义了 $tries 属性。否则,哪怕你没设任何限制,它也只执行一次就进失败表。
常见误解是以为“只要没配 tries = 0 就会重试”,其实不然——如果既没在命令行指定 --tries,任务类里也没声明 $tries,那失败就是真的失败,不重试、不延迟、不回调。
- worker 启动时加
--tries=3:所有该进程消费的任务都统一按此重试次数走 - 任务类中写
public $tries = 5;:优先级更高,会覆盖命令行参数 - 两者都没设 → 失败即终止,不重试
-
php artisan queue:retry是人工干预入口,和自动重试机制完全无关
重试间隔由 $backoff 或 retryUntil 决定,不是固定值
$backoff 是最常用控制退避时间的方式,但它不是“每次等 X 秒”,而是按失败次数索引数组:public $backoff = [1, 3, 5]; 表示第 1 次失败后延 1 秒重试,第 2 次失败后延 3 秒,第 3 次失败后延 5 秒。若数组长度小于 $tries,后续失败会复用最后一个值。
而 retryUntil() 更适合时效敏感任务,比如支付确认必须在 15 秒内完成。它设定的是一个绝对截止时间点,不是间隔:
public function handle()
{
$this->retryUntil(now()->addSeconds(15));
}
注意:retryUntil 和 $tries 互斥——只要定义了 $tries,retryUntil 就会被忽略。
- Redis 驱动支持毫秒级 delay,但需配合
dispatch(...)->delay(now()->addMilliseconds(100))手动调用 - database 驱动不支持亚秒级 delay,最小单位是秒
- 使用
retryUntil时,确保队列驱动支持延迟(Redis/Amazon SQS 可,database 不推荐)
重试失败任务前,先检查 payload 是否可反序列化
人工执行 php artisan queue:retry 123 时,框架会从 failed_jobs 表读取 payload 字段并反序列化重建任务对象。一旦这个字段损坏或被截断,就会报错:ErrorException: unserialize(): Error at offset。
这通常发生在:
- MySQL 的
TEXT字段存不下长 payload(尤其含 base64 图片或大数组) - 任务里序列化了不可持久化的资源,如
cURL handler、file pointer、闭包或$request实例 - 开发环境用 SQLite,生产用 MySQL,字段长度限制不一致导致 silently 截断
验证方法:直接查表 SELECT payload FROM failed_jobs WHERE id = 123,粘贴到 unserialize() 测试是否出错。修复方向是缩短 payload(拆分数据、存 ID 而非全量)、改用 JSON 序列化(需自定义 __serialize),或升级字段类型为 MEDIUMTEXT。
高优失败任务不能只靠 retry 命令,得隔离队列+独立 worker
用 php artisan queue:retry all 重试全部失败任务,本质是把所有记录重新推回原队列(比如 default)。如果此时高优任务(如支付回调)和低优任务(如日志归档)混在同一队列,它们的重试会互相阻塞——一个慢任务卡住整个队列,高优任务反而更晚执行。
真正可控的做法是物理隔离:
- 创建专用失败队列名,如
urgent_failed,并在任务failed()方法里手动dispatch(...)->onQueue('urgent_failed') - 单独起一个 worker:
php artisan queue:work --queue=urgent_failed --tries=3 --timeout=60 - 配合 Horizon,在
config/horizon.php中为urgent_failed配置更高balance权重或独占 supervisor - 避免用
queue:retry往高优队列推——它默认推回原连接的 default 队列,除非加--queue=urgent_failed
最关键的遗漏点:很多人以为“设了 $queue = 'urgent' 就够了”,但 failed_jobs 表本身不存队列名,重试时无法还原。必须在 failed() 里主动指定新队列,或用自定义失败表(如 priority_failed_jobs)额外存 queue 字段。











