Job batching 是 laravel 8.5+ 原生队列批处理机制,需 database/redis 驱动,通过 Bus::batch() 创建含 then/catch/finally 回调的 Batch 实例并 dispatch() 异步执行,状态由 job_batches 表自动维护,前端应通过 BatchRepository 查询进度。

Job Batching 是 Laravel 8.5+ 原生支持的特性,不是插件或第三方包
如果你还在用 dispatch() 逐个推任务、再靠数据库字段手动记进度,说明你没启用 Laravel 自带的批处理机制。Job Batching 要求最低 Laravel 版本为 8.5,且必须使用 database 或 redis 作为队列驱动(sync 不支持)。它本质是把一批 Job 封装成一个 Batch 实例,由框架统一追踪完成数、失败数、取消状态等元数据。
如何创建并分发一个 Batch(含实际参数控制)
不要用 Bus::batch() 后直接 dispatch() —— 这会立刻执行,失去异步意义。正确流程是先构建 Batch,再调用 dispatch() 推入队列:
use IlluminateSupportFacadesBus; $batch = Bus::batch([ new SendNotificationJob($user1), new SendNotificationJob($user2), new SendNotificationJob($user3), ])->then(function (Batch $batch) { Log::info('所有通知发送完成', ['batch_id' => $batch->id]); })->catch(function (Batch $batch, Throwable $e) { Log::error('批处理中发生错误', ['batch_id' => $batch->id, 'error' => $e->getMessage()]); })->finally(function (Batch $batch) { Log::info('无论成功失败都会执行', ['batch_id' => $batch->id]); })->dispatch(); // 注意:此时 $batch->id 已生成,但尚未开始执行 // 可立即用于前端轮询或存入 session
-
Bus::batch([...])接收 Job 实例数组,每个 Job 必须实现ShouldQueue -
then()/catch()/finally()回调在「整个 Batch 生命周期结束时」触发,运行在单独的 Job 中(需确保该 Job 也能被正常消费) - 默认超时为 24 小时,可通过
timeout()方法修改:->timeout(3600)
前端如何实时查 Batch 进度(关键字段和查询方式)
Batch 状态不依赖你自建表,Laravel 在 job_batches 表中自动维护。最常用字段是:total_jobs、pending_jobs、failed_jobs、finished_jobs、failed_job_ids(jsON 字符串)、options(含 timeout 等)。查询示例:
// 控制器中提供进度接口 public function batchStatus(string $batchId) { $batch = IlluminateBusBatchRepository::make()->find($batchId); if (! $batch) { return response(['message' => 'Batch not found'], 404); } return response([ 'id' => $batch->id, 'name' => $batch->name ?? 'unnamed', 'progress' => $batch->progress(), // 自动计算百分比(0–100) 'status' => $batch->status(), // 'pending'|'running'|'finished'|'cancelled'|'failed' 'total' => $batch->totalJobs, 'processed' => $batch->finishedJobs + $batch->failedJobs, 'failed' => $batch->failedJobs, 'cancelled' => $batch->cancelledJobs, ]); }
-
$batch->progress()是安全的,即使部分 Job 还没被消费,也会按已知总数估算(例如 5/10 → 50%) - 不要用
DB::table('job_batches')->where(...)->first()直接查——可能读到未刷新的缓存或脏数据;务必走BatchRepository - 前端轮询建议间隔 ≥ 2 秒,避免压垮队列驱动(尤其是 Redis)
常见失败场景和绕过坑点
Batch 失败不等于所有 Job 都失败,但某些配置会让整个 Batch 卡死:
- 某个 Job 抛出未被捕获的异常,且没定义
catch()回调 → Batch 状态变为failed,剩余 pending Job 不再执行 - Job 内部调用
sleep()或阻塞 I/O(如同步 http 请求)→ 可能导致单个 Job 超时,进而触发failed,但job_batches.failed_job_ids只存 ID,不存错误堆栈 - 使用
database驱动时,若队列 worker 意外退出(如 OOM),Batch 状态可能卡在running→ 需配合php artisan queue:restart和定期清理脚本 - Batch 名称(
name())默认为空,调试困难 → 强烈建议显式命名:->name('user-import-batch-'.$importId)
真正难排查的是「Batch 显示 finished,但部分 Job 实际没执行」——这通常是因为 Job 构造函数中传入了不可序列化的对象(如 Eloquent Model 实例),导致反序列化失败、静默丢弃。务必只传 ID 或基础类型。