
别把 Bus::chain() 当成数据库事务用——它只保顺序,不保回滚。链里任何一步写库成功、后续失败,前面的变更就留在那里了。
什么时候必须用 Bus::chain() 而不是手动 dispatch()
只有当步骤之间存在强因果依赖,且你明确接受“失败即中断、不撤回”时才适用:
- ✅ 生成缩略图 → 上传 CDN → 更新 media 字段:后一步必须用前一步产出的文件 URL
- ✅ 发送支付回调通知 → 标记订单为已通知 → 推送消息到 WebSocket:状态流转不可逆,中间断掉就不该继续
- ❌ 扣库存 + 创建订单:这需要原子性,得用数据库事务或 Saga 模式,不是任务链的事
- ❌ 多个独立通知(邮件、短信、站内信):它们可并行,用
Bus::batch()更合适
Bus::chain() 传参为什么不能传模型实例
因为每个任务在独立队列进程里反序列化执行,模型实例带连接、关系、原始 SQL 查询器,PHP 序列化会直接报错:Serialization of 'Closure' is not allowed 或 Connection lost。
- ✅ 正确做法:只传 ID,比如
new ProcessOrder($orderId),然后在handle()里查Order::findOrFail($this->orderId) - ✅ 多个关联 ID:用数组或 DTO 封装,但字段仍限于
int、string、bool等基础类型 - ❌ 避免在构造函数里调
$order->load('items')或$order->toArray()——加重序列化负担,且可能因连接关闭失败
链中任务怎么共享 trace_id 或用户 ID
HTTP 请求里的 request()->id 或 Auth::id() 在队列进程里全失效,不能靠闭包捕获,也不能依赖全局变量。
- ✅ 最稳方式:显式注入构造函数,比如
new NotifyUserJob($userId, $trace_id),并在日志处理器中统一注入$this->trace_id - ✅ 长链场景:把
$trace_id存进job_logs表,用$job->id关联,避免参数层层透传 - ❌ 别在
withChain()里写function () use ($userId) { ... }——闭包无法序列化,运行时找不到上下文
失败后怎么补救,而不是指望 catch()
->catch() 回调只负责记录或发告警,它不会、也不能帮你撤回已执行的任务。
- ⚠️ 链是
[DeductStock, CreateOrder, SendSms]?第三步失败,前两步的 DB 写入已生效,状态就脏了 - ✅ 补偿方案一:每个任务自己实现反向操作,比如
CreateOrder::reverse()主动恢复库存 - ✅ 补偿方案二:改用状态机模型(如
OrderProcessing),每步更新一个status字段,失败后由 watchdog 任务扫描并触发修复流程 - ❌ 不要以为
->catch(fn () => DB::rollback())有用——队列没事务上下文,DB::rollback()压根没东西可回滚
真正难的从来不是怎么把任务串起来,而是想清楚哪一步失败了谁来兜底、数据怎么回到一致状态。Laravel 不替你做这个判断,它只提供执行顺序的控制权。











