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 Illuminate\Support\Facades\Bus;
$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 = \Illuminate\Bus\BatchRepository::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 或基础类型。










