登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

Laravel 队列重试次数怎样与失败记录配合

来源:17golang原创

时间:2026-11-12 00:26:55 185浏览 收藏

Laravel 队列重试次数怎样与失败记录配合

Laravel 队列的重试次数和失败记录不是两个独立开关:worker 或作业类先决定一个作业最多尝试多少次,异常、超时以及某些中间件或手动 release() 都可能消耗尝试;只有作业耗尽允许的尝试后,才会进入失败作业存储。官方队列文档地址:https://laravel.com/docs/12.x/queues

可以把处理过程记成“尝试 → 退避 → 再尝试 → 达到边界后记录失败”。先把暂时性故障和永久性故障分开,再决定是否用 queue:retry 重新投递,不要看到 failed_jobs 就无条件批量重试。
  • 最大尝试次数:由 queue:work --tries 或作业类的 $tries、tries() 决定。
  • 失败记录时机:作业耗尽尝试、持续超时或最终抛出异常后,才写入 failed_jobs。
  • 重试等待:用 worker 的 --backoff 或作业的 $backoff/backoff() 拉开间隔。
  • 人工动作:先用 queue:failed 查看失败上下文,修复原因后再按 ID 执行 queue:retry。

先区分一次尝试和一条失败记录

worker 从队列取出作业并执行一次,就产生一次尝试。Laravel 文档还特别说明,未处理异常、手动释放、WithoutOverlapping 或 RateLimited 等中间件释放,以及超时,都可能消耗尝试次数。因此“handle() 完整执行过几次”和“attempts 计数是多少”不能简单画等号。

Laravel 队列作业从尝试次数到 failed_jobs 失败记录的静态关系说明图
图1:Laravel 队列尝试、达到上限和失败记录之间的静态说明图,不是运行截图。

如果作业仍有剩余尝试,异常通常会让它回到队列;达到最大尝试后,Laravel 才把它视为 failed job。失败记录保存的是后续人工判断所需的线索,例如作业 ID、连接、队列、失败时间和异常信息,而不是“自动修复已经完成”的证明。

用 worker 和作业类设定重试上限

最粗粒度的配置是在启动 worker 时指定 --tries。如果某个作业需要自己的边界,可以在作业类中声明 $tries 或提供 tries() 方法。作业类的配置适合表达业务差异,例如发送通知和生成报表可能不应该共享同一个重试上限。

# worker 默认最多尝试 3 次,所有未单独声明的作业沿用这个边界
php artisan queue:work redis --tries=3

# 只查看失败作业的 ID、连接、队列和失败时间,先收集上下文再处理
php artisan queue:failed
sync($this->invoiceId);
    }

    public function __construct(private readonly int $invoiceId)
    {
        // 只保存可序列化的标识,处理时重新读取最新业务数据。
    }
}
?>

如果同时设置 worker 的 --tries=3 和作业类的 $tries=5,应以作业类规则作为该作业的细粒度配置。不要把 --tries=0 当成“失败自动解决”;官方文档将它定义为无限重试,只有确定故障可以最终恢复时才适合采用。

用 backoff、retryUntil 和 timeout 拉开边界

默认情况下,异常后的作业可能立即重新入队。依赖外部 API、数据库或限流服务时,紧密重试会进一步放大故障。可以用 worker 的 --backoff 或作业类的 backoff 属性/方法设置等待时间,也可以返回一组等待秒数形成逐步退避。

addMinutes(10);
    }
}
?>

retryUntil() 用时间边界控制重试时段;如果它和 tries 同时存在,Laravel 文档说明优先使用时间边界。另一个容易混淆的参数是 worker 的 --timeout:它限制单次执行时长,而不是等待下一次重试的时长。队列连接的 retry_after 也要和 timeout 配合,通常应让 timeout 比 retry_after 短几秒,避免冻结作业被重复处理。

Laravel worker tries、作业 tries、retryUntil 和 backoff 共同影响失败处理的静态结构图
图2:Laravel 队列重试控制项与继续尝试或写入失败记录的静态结构图,不是运行截图。

达到上限后怎样阅读 failed_jobs

新应用通常已经包含失败作业表迁移;如果项目没有,可以先生成失败作业表迁移并执行迁移。正式环境中,先确认失败存储没有被显式关闭,再用命令查看记录:

# 没有失败作业表时生成迁移,然后按项目流程执行数据库迁移
php artisan make:queue-failed-table
php artisan migrate

# 查看失败作业,记录目标 ID 和异常原因
php artisan queue:failed

# 只对已经修复原因的指定作业重新投递,避免无差别重试全部记录
php artisan queue:retry ce7bb17c-cdd8-41f0-a638-9b4fef4e5ece

失败记录代表一次处理链路已经到达边界,不代表下一次 queue:retry 一定成功。若异常来自错误参数、已删除资源或不具备幂等性的外部副作用,应先修复代码或数据,再选择指定 ID 重试。批量重试适合有明确共同根因且已经修复的记录;否则容易让同一种错误再次制造大量失败。

把最终失败处理和幂等性收口

作业可以定义 failed(?Throwable $exception),在最终失败时发送通知、更新业务状态或留下可检索的关联 ID。要注意,Laravel 会在调用 failed 前重新实例化作业,handle 中临时修改过的属性不会可靠地带到失败回调,因此关键上下文应落在持久化记录或结构化日志中。

exportOnce($this->reportId);
    }

    public function failed(?Throwable $exception): void
    {
        // 最终失败只做通知和状态收口,不在这里再次 dispatch 自己。
        app(FailureNotifier::class)->notify($this->reportId, $exception);
    }
}
?>

如果作业通过中间件限流、避免重叠或手动释放来等待资源,要把这些释放也算进尝试预算;必要时增加 $tries,同时给出合理的 backoff。对于连续抛出异常的第三方依赖,还可以使用 Laravel 的异常节流中间件,把“允许多少次异常”和“进入节流后等待多久”分开设计。

现象先看什么下一步
作业瞬间重复执行backoff、异常类型和 worker 参数增加退避,确认瞬时故障不会触发紧密重试
作业很快进入 failed_jobs$tries、retryUntil、timeout 与释放次数重新计算尝试预算,不要只把上限改成无限
retry 后仍然失败失败记录中的异常和业务数据先修复根因与幂等性,再重试指定 ID
同一订单被处理多次外部副作用是否有幂等键让成功写入和重试判断可以安全重复执行

常见问题

Laravel 队列默认会重试几次?

如果 worker 没有设置 --tries,且作业类也没有自己的尝试配置,官方文档说明作业通常只尝试一次。使用中间件释放或手动 release() 时,应明确增加尝试预算。

失败记录是不是每次异常都会新增一条?

不是。还有剩余尝试时,作业会重新进入队列;耗尽允许的尝试后才会作为 failed job 保存。最终异常可能是业务异常,也可能是 MaxAttemptsExceededException 或超时异常。

可以直接执行 queue:retry all 吗?

可以执行,但应先确认所有失败记录的共同根因已经修复,并且作业具备幂等性。只想验证一个修复时,优先按具体失败 ID 重试。

清理失败记录会重新执行作业吗?

不会。queue:forget 或 queue:flush 只删除失败记录;需要重新投递时使用 queue:retry,需要清理历史数据时再使用忘记或清空命令。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>