Laravel批量分发任务需先用Bus::batch()正确调度(满足Laravel≥8.5、database/redis驱动、Job含Batchable trait),再通过启动多个queue:work进程实现并发消费,二者缺一不可。

批量分发任务本身不等于并发执行——Laravel 默认的 dispatch() 一次只接受一个任务实例,硬传数组会直接报 Serialization of 'Closure' failed 或 TypeError。真要批量 + 并发,得拆成两步:先正确分发(批量),再确保有足够 worker 消费(并发)。两者缺一不可。
Bus::batch() 是唯一安全的批量调度入口
它不是“把一堆任务塞进一条队列记录”,而是为每个任务生成独立队列条目,同时在 job_batches 表写入批次元数据,支持 then()、catch()、finally() 回调。但三个条件必须同时满足:
-
QUEUE_CONNECTION不能是sync(否则静默同步执行,job_batches不写入,$batch->id为空) - 队列驱动必须是
database或redis(beanstalkd、sqs不支持批次状态跟踪) - 每个 Job 类必须
use Illuminate\Bus\Batchable;(漏掉这个 trait,整个批次构建失败但不报错)
示例中若忘记加 Batchable,Bus::batch($jobs)->dispatch() 看似成功,实际 job_batches 表无记录,后续所有回调都不会触发。
大批量任务(如 5000+)别硬塞进 Bus::batch()
虽然语义清晰,但隐患明显:
- 用
database驱动时,单次插入 5000 条jobs记录可能锁表或超时(尤其 MySQL 默认max_allowed_packet不够) - 用
redis驱动时,若连接池配置低或内存紧张,LPUSH批量入队阶段就可能失败 -
job_batches表里那条记录的timeout字段默认是 24 小时,从dispatch()调用那一刻开始倒计时——不是从第一个 job 执行起算。5000 个任务若平均耗时 2 秒,理论需近 3 小时,但若中间卡顿或 worker 重启,超时后未完成的 job 会被框架强制标记为失败
更稳的做法是分块 + 异步触发:collect($ids)->chunk(50)->each(fn ($chunk) => dispatch(new ProcessChunk($chunk)))。每个 ProcessChunk 内部再串行或小批量处理,压力可控、可监控、易重试。
并发消费靠多个 queue:work 进程,不是靠 dispatch 方式
很多人误以为“用了 Bus::batch() 就自动并发了”,其实不然。批次只是调度和状态管理机制,真正并行执行依赖 worker 数量:
- 单个
php artisan queue:work进程永远是串行消费——哪怕你分发了 1000 个任务,它也一个接一个跑 - 要并发,必须启动多个独立进程:
php artisan queue:work --queue=default --sleep=3 --tries=3开 4 个终端,或用 Supervisor 配numprocs=4 - 底层靠队列驱动的原子出队保证不重复:Redis 用
LPOP+ Lua 脚本,Database 用事务 +SELECT ... FOR UPDATE,无需额外加锁
注意 --timeout 值必须大于单个任务最坏耗时(比如验证码任务设 --timeout=90),否则 worker 会在任务中途被杀,导致任务丢失或重复入队。
Job 构造函数里别传 Request、File 或 DB 连接
这些对象不可序列化,会导致 dispatch() 或 Bus::batch() 报错。常见错误模式:
-
new SendEmail(request())→request()是闭包,序列化失败 -
new ProcessImage($request->file('avatar'))→UploadedFile含资源句柄,无法序列化 -
new NotifyUser(auth()->user())→AuthManager实例含闭包和连接,序列化炸
正确做法:只传简单类型(int、string、array)或 Eloquent 模型 ID,在 handle() 中重新查库。例如:new SendEmail($user->id),然后在 handle() 里 $user = User::find($this->userId)。
最容易被忽略的是批次 timeout 和 worker timeout 的双重约束:前者管整个批次生命周期,后者管单个任务执行窗口。两个都得显式设,且值要合理匹配实际负载,否则看似配置了并发,实则大量任务在途中被截断或降级为串行。


















