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

Laravel 队列批处理的失败项重试边界

来源:17golang原创

时间:2026-10-10 21:50:09 121浏览 收藏

做 CSV 导入、订单同步或批量通知时,我通常不会把“某个 Job 失败”直接等同于“整个批次要重跑”。Laravel 的队列批处理把失败拆成两层:Job 自己有尝试次数和退避时间,Batch 又有取消、回调和失败项重试语义。真正稳妥的做法,是先决定失败属于哪一层,再决定由 worker 自动尝试,还是由人确认后重试。

官方地址:https://laravel.com/docs/13.x/queues

Laravel 批处理默认会在 Job 失败后把 Batch 标记为 cancelled;allowFailures 可以改变这个批次级行为,而 queue:retry-batch 面向的是指定批次中已经失败的 Job。它们不是同一个“重跑全部任务”按钮。

先分清两层失败:Job 与 Batch

假设一个批次负责同步 500 个客户,每个 Job 处理一段明确的客户范围。第 37 段因为上游接口临时超时,属于单项任务的瞬时失败;但如果同一批所有 Job 都因为凭证失效而失败,问题就已经上升到批次或系统配置层。两种情况都显示“失败”,处理动作却不能相同。

Job 层关心的是一次任务能尝试多少次、两次尝试之间等多久、哪些异常值得继续尝试。Batch 层关心的是失败后是否取消批次、何时执行 catch、是否允许剩余任务继续以及最终如何收尾。把两层混为一谈,最常见的结果就是让临时网络错误触发整批重复写入。

Laravel Job、Batch 状态、失败记录和批次重试命令之间的静态关系说明图
图1:Laravel 队列批处理失败边界说明图,展示 Job、Batch 状态、失败记录与重试命令的关系;这是静态说明图,不是截图或运行证据。

用 tries 和 backoff 限定单个失败项

第一道边界放在 Job 自己。worker 可以用 --tries 统一设置尝试次数,也可以在 Job 类中使用 $tries;$backoff 或 worker 的 --backoff 用来避免瞬时故障时立即密集重试。需要按时间截止的任务,则可以用 retryUntil 让重试窗口比固定次数更贴近业务。

batch()?->cancelled()) {
            return;
        }

        // 每个客户使用稳定业务键,重复尝试也不会重复落库。
        $gateway->syncRange($this->start, $this->end);
    }
}

这里的 tries 只决定这个 Job 的自动尝试边界,不代表批次成功,也不会把已经成功的其他 Job 重新执行。批量写入必须先具备幂等性,例如用客户编号和同步批次号组成唯一键,或者让下游接口支持幂等请求。

用 catch、allowFailures 和 finally 定义批次语义

批次回调描述的是批次状态变化后的业务动作。Laravel 官方文档指出,批次 Job 失败时会触发已注册的 catch,并且这个回调只针对批次中的第一次失败;默认情况下,失败会让批次进入 cancelled。若业务允许“成功项先保留,失败项单独补偿”,可以使用 allowFailures,但这并不替你解决失败项的补偿策略。

catch(function (Batch $batch, Throwable $error): void {
    // 只记录首个失败信号,详细失败项交给失败记录与监控查询。
    logger()->error('customer sync batch failed', [
        'batch_id' => $batch->id,
        'error' => $error->getMessage(),
    ]);
})->finally(function (Batch $batch): void {
    // 无论成功、取消还是部分失败,都更新批次汇总状态。
    logger()->info('customer sync batch finished', [
        'batch_id' => $batch->id,
        'failed_jobs' => $batch->failedJobs,
    ]);
})->allowFailures()->dispatch();

若“任一失败就必须停止后续业务动作”,保留默认取消语义通常更直观;若允许部分成功,则要在业务表中保存每个分片的状态,不要只看 Batch 的总体进度。finally 适合做收尾记录,不能被当作“所有 Job 都成功”的证明。

把人工重试入口绑定到批次 UUID

自动尝试耗尽后,失败 Job 会进入失败记录。Laravel 提供 queue:retry-batch,通过 Batch UUID 重试指定批次中失败的 Job。这个入口适合放在运维脚本或后台动作之后,但不适合无条件定时执行:如果根因是错误数据或失效凭证,重复重试只会扩大噪声。

# 只重试已确认根因已恢复的批次失败项
php artisan queue:retry-batch 32dbc76c-4f82-4749-b610-a639fe0099b5

# 查询失败任务,先确认异常类型与影响范围
php artisan queue:failed

人工动作至少要带三项信息:批次 UUID、失败原因、是否已经完成幂等检查。对外部接口超时可以在恢复后重试;对字段校验失败则应先修正数据;对权限或凭证错误,应先修复配置再放行。不要因为 catch 被调用过一次,就推断所有失败项都具有相同根因。

Laravel 自动尝试、批次回调、人工重试和幂等键之间的静态决策关系图
图2:Laravel 失败项重试决策说明图,展示配置、批次回调、人工入口与幂等边界的静态关系;这是结构图,不是产品界面截图。

上线前用一张表检查重试边界

问题主要控制点推荐动作
临时网络错误tries、backoff限定次数,延迟后自动尝试
单项数据错误Job 失败记录、业务状态修正数据后只重试失败项
任一失败都不能继续Batch 默认取消语义、catch停止后续批次动作,保留首个失败信号
允许部分成功allowFailures、分片状态单独记录成功与失败分片,不把 Batch 总状态当业务结果
人工重试queue:retry-batch、幂等键确认根因恢复后按 UUID 重试

几个容易误判的问题

失败的 Job 会让整个 Batch 重新开始吗?

不会。默认语义是批次被标记为 cancelled;自动重试发生在 Job 层。需要人工重试时,使用批次 UUID 重试失败项,而不是重新构造一份包含全部 Job 的新批次。

allowFailures 是不是等于忽略错误?

不是。它改变的是失败是否自动取消批次;失败记录、失败计数和业务补偿仍然需要处理。只有在业务明确允许部分成功时才使用。

为什么只记录 catch 里的异常不够?

因为官方语义下批次 catch 只响应第一次失败。若要知道每个失败分片,应结合 Job 失败记录、批次 UUID、业务分片状态和监控关联键。

Laravel 队列批处理的关键不是把重试次数调大,而是把自动尝试、批次状态和人工补偿分别建模。先让 Job 对瞬时错误有限重试,再让 Batch 明确“失败即取消”还是“允许部分失败”,最后用 UUID 和幂等键控制人工重试范围,失败项才不会从可恢复问题变成重复写入事故。

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