Laravel队列Job的WithoutOverlapping中间件未按预期工作
问题原因分析
进程数设为1时订单编号正常,进程数为3时出现重复,核心问题出在并发控制的可靠性上,具体分为两种可能:
WithoutOverlapping中间件锁未生效
该中间件默认使用应用的cache驱动存储锁,如果你的cache配置的Redis连接,与Job中操作订单编号的Redis连接不一致(比如分属不同Redis实例/数据库),锁就无法跨进程生效。此时多个Worker进程会同时取出同一firm的任务,绕过锁的限制并发执行,导致get+set的非原子操作生成重复编号。非原子操作的固有风险
即使中间件锁正常工作,当前代码中getNextNumber的redis.get()与后续的redis.set()是两步独立操作,在锁释放的瞬间,若多个任务同时触发,仍可能出现读取到相同旧值的情况(极端场景)。
解决方案
方案一:修复WithoutOverlapping中间件锁
确保中间件使用的Redis连接与Job操作订单编号的连接一致:
public function middleware(): array { // 显式指定使用redis缓存存储锁,对应config/cache.php中的redis store return [ (new WithoutOverlapping($this->firm->id))->store('redis') ]; }
同时检查config/cache.php的redis配置,确保其connection与Job中Redis::connection()使用的连接一致:
// config/cache.php 'redis' => [ 'driver' => 'redis', 'connection' => 'default', // 与Job中Redis连接保持一致 ],
方案二:使用Redis原子操作(更可靠)
直接用Redis的incr原子递增命令替代get+set,彻底避免并发问题,无需依赖队列中间件锁:
public function handle(): void { $currentDay = Carbon::now()->format('Y-m-d'); $redisKey = 'firm-order-number.' . $currentDay . '.' . $this->firm->id; // 原子递增操作,保证每次调用都返回唯一的下一个编号 $nextOrderNumber = $this->redis->incr($redisKey); $this->order->number = $nextOrderNumber; $this->order->save(); }
这种方式从底层保证了编号生成的唯一性,不受队列进程数、锁配置等因素影响,是生成连续唯一订单编号的最优方案。
内容的提问来源于stack exchange,提问作者mattyh88
相关产品推荐
相关产品推荐

