redis+lua脚本实现库存原子性扣减是秒杀防超卖的核心方案,通过将“查库存+判断+扣减”封装为单线程原子操作,彻底避免并发冲突;batchexecutor仅用于订单生成后的批量入库,不可用于合并扣减请求。

线程池本身不直接合并请求,也不能替代数据库批量操作逻辑。在秒杀场景中,把“高并发请求合并”和“BatchExecutor”混在一起使用,容易引发库存超卖、状态不一致等严重问题。真正安全高效的方案,是分层解耦:前端用消息队列或阻塞队列做请求缓冲与削峰,后端用原子校验+异步落库,而 BatchExecutor 仅在**确认订单已合法生成后**,用于批量写入数据库。
秒杀扣减不能靠线程池“合并请求”
很多人误以为:把多个扣减请求塞进同一个线程池任务里,就能“合并成一条 SQL”。这是危险误区——
- 线程池只负责执行任务,不感知业务语义,无法自动识别哪些请求该合并、哪些该拒绝
- 若在 Runnable 中手动聚合多个用户 ID 去批量 update 库存,会绕过 Redis Lua 原子校验,导致超卖
- 一个任务失败(如部分用户余额不足),整批更新回滚,影响其他合法请求
正确分层:校验、排队、异步、批量
高并发秒杀的合理链路是:
- 第一层(入口):用 Redis + Lua 脚本完成原子性库存扣减与限购判断,失败请求立即返回
- 第二层(缓冲):校验成功的请求封装为 OrderTask,投递到 ArrayBlockingQueue 或 Kafka,实现流量整形
- 第三层(消费):固定数量消费者线程(或 Java 25 虚拟线程)从队列拉取任务,逐条生成订单实体
- 第四层(入库):将一批已生成的订单(比如每 50 条),交由 MyBatis BatchExecutor 批量 insert
BatchExecutor 只作用于“已确认合法”的订单写入
它不是用来合并扣减请求,而是优化“订单持久化”这一步的 I/O 效率。示例用法:
- 消费线程收集 50 个已通过风控、支付校验的订单对象
- 调用 SqlSession 的 batch 模式:sqlSession.insert("insertOrder", order) 循环添加
- 调用 sqlSession.flushStatements() 触发批量提交
- 注意:每个批次必须在同一个事务内完成,且不能跨用户会话混批
警惕常见错误配置
以下做法在线上极易出事:
- 用 LinkedBlockingQueue 配合无界队列承接秒杀流量 → 内存溢出、GC 频繁
- 把库存扣减逻辑和订单生成放在同一个线程池任务里 → 任务耗时波动大,线程池阻塞雪崩
- BatchExecutor 批次大小设为 1000+ → 单次事务过大,数据库锁表时间长,反而降低吞吐
- 未对 BatchExecutor 的异常做重试/降级 → 批量失败后订单丢失,资损
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











