这次改造的核心目标是把秒杀的数据库操作异步化用阻塞队列 线程池实现「请求快速响应、后台异步处理订单」彻底解决秒杀场景下的数据库压力瓶颈。一、新增核心组件阻塞队列与线程池// 阻塞队列存放秒杀订单任务 private BlockingQueueVoucherOrder orderTasks new ArrayBlockingQueue(1024 * 1024); // 异步处理线程池 private static ExecutorService SECKILL_ORDER_EXECUTOR Executors.newSingleThreadExecutor();关键说明ArrayBlockingQueue是有界队列固定容量 1024*1024可承载百万级秒杀订单任务防止无限堆积导致内存溢出具备阻塞特性队列满时生产者入队会阻塞队列空时消费者取任务会阻塞天然适配生产者 - 消费者模型秒杀接口作为生产者往队列放订单后台线程作为消费者从队列取订单处理线程安全无需手动加锁即可在多线程环境下安全入队、出队。newSingleThreadExecutor内部只有一个工作线程串行依次处理每一个秒杀订单保证订单按到达顺序依次处理避免多线程并发操作同一张订单表、同一条商品库存记录引发数据错乱、超卖、重复下单等问题线程池全局静态唯一项目生命周期内只创建一次复用线程减少线程创建销毁开销。二、类初始化时启动消费者线程PostConstruct private void init() { SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler()); }详解PostConstructSpring 生命周期注解当前 Bean 被 Spring 容器初始化完成后立刻自动执行不用手动调用项目一启动就自动开启消费者监听线程提前待命不用等用户发起秒杀才创建线程将自定义任务VoucherOrderHandler提交给单线程池开始无限循环监听阻塞队列等待秒杀订单任务进入。三、消费者线程实现VoucherOrderHandlerprivate class VoucherOrderHandler implements Runnable { Override public void run() { while (true) { try { // 1. 从阻塞队列获取订单信息队列空时自动阻塞 VoucherOrder voucherOrder orderTasks.take(); // 2. 处理订单 handleVoucherOrder(voucherOrder); } catch (Exception e) { log.error(处理订单异常, e); } } } }解析while(true)开启死循环常驻监听只要项目不停止就一直监听队列任务orderTasks.take()阻塞式获取元素队列里没有订单任务时线程会进入阻塞休眠状态不占用 CPU 资源一旦有订单入队立刻被唤醒处理try-catch 捕获异常单个订单处理报错不影响整体循环避免一个订单异常导致整个秒杀消费线程挂掉所有数据库下单、扣库存逻辑都放到异步线程中执行和前端请求主线程完全解耦。四、订单处理方法handleVoucherOrderprivate void handleVoucherOrder(VoucherOrder voucherOrder) { // 1. 获取用户ID从订单对象中取避免ThreadLocal失效 Long userId voucherOrder.getUserId(); // 2. 创建Redisson锁 RLock lock redissonClient.getLock(lock:order: userId); // 3. 尝试获取锁 boolean isLock lock.tryLock(); if (!isLock) { log.error(不允许重复下单); return; } try { // 4. 获取代理对象解决事务失效问题 proxy.createVoucherOrder(voucherOrder); } finally { // 5. 释放锁 lock.unlock(); } }关键变更1、用户 ID 不从 UserHolder 获取异步线程不属于前端请求的主线程ThreadLocal 中的用户信息会失效、拿不到数据所以只能从已经封装好的voucherOrder订单对象中获取用户 ID。2、引入Redisson分布式锁即使 Redis Lua 脚本已经做了一人一单前置校验这里仍加分布式锁做业务兜底防止网络重试、重复请求导致重复下单保证同一个用户同一时间只能处理一个秒杀订单。3、必须使用代理对象调用事务方法异步线程中如果直接this.createVoucherOrder()会绕过 Spring AOP 代理导致 Transactional 事务注解失效通过AopContext.currentProxy()获取当前类代理对象调用方法才能让事务生效保证扣库存、创订单的原子性。4、finally 释放锁保证无论业务正常执行还是抛出异常分布式锁都一定会释放避免死锁。五、秒杀入口方法改造快速响应 入队Override public Result seckillVoucher(Long voucherId) { // 获取用户ID Long userId UserHolder.getUser().getId(); // 1. 执行Lua脚本库存、一人一单校验扣减 Long result stringRedisTemplate.execute(...); int r result.intValue(); if (r ! 0) { return Result.fail(r 1 ? 库存不足 : 不能重复下单); } // 2. 秒杀成功构建订单对象 VoucherOrder voucherOrder new VoucherOrder(); long orderId redisIdWorker.nextId(order); voucherOrder.setId(orderId); voucherOrder.setUserId(userId); voucherOrder.setVoucherId(voucherId); // 3. 放入阻塞队列生产者 orderTasks.add(voucherOrder); // 4. 返回订单ID直接响应前端 return Result.ok(orderId); }关键变更所有复杂校验下沉到 Redis Lua原子完成库存判断、一人一单判断、Redis 库存扣减杜绝超卖和重复下单性能远高于 Java 层多次判断 DB。请求主线程不再操作数据库只做 Lua 脚本调用、封装订单、入队操作全程都是内存和 Redis 操作接口响应速度极快。异步解耦订单真正入库、扣数据库库存全部交给后台消费线程慢慢处理前端不用等待数据库 IO体验无感。全局唯一订单 ID通过 Redis 全局 ID 生成器生成 orderId提前返回给前端前端可凭订单 ID 查询后续订单状态。六、创建订单方法改造异步处理 参数变更Override Transactional public void createVoucherOrder(VoucherOrder voucherOrder) { // 1. 从订单对象获取用户ID和优惠券ID Long userId voucherOrder.getUserId(); Long voucherId voucherOrder.getVoucherId(); // 2. 一人一单校验兜底 int count query().eq(user_id, userId).eq(voucher_id, voucherId).count(); if (count 0) { log.error(用户已经购买过一次); return; } // 3. 扣减库存乐观锁 boolean success seckillVoucherService.update() .setSql(stock stock - 1) .eq(voucher_id, voucherId).gt(stock, 0) .update(); if (!success) { log.error(库存不足); return; } // 4. 保存订单 save(voucherOrder); }改造说明方法参数由原来的优惠券 ID改为完整订单对象适配异步线程无 ThreadLocal 的场景保留数据库层一人一单兜底校验形成 Redis 前置拦截 DB 最终兜底的双层防护采用乐观锁扣库存gt(stock,0)保证库存大于 0 才扣减防止超卖添加Transactional事务注解保证扣库存、新增订单要么同时成功要么同时回滚数据一致不错乱。总结这次改造的核心目的通过阻塞队列 线程池实现秒杀请求的异步处理把数据库操作从请求主线程剥离实现「前端快速响应、后台异步处理订单」大幅提升系统并发能力同时解决秒杀场景下的数据库压力瓶颈。 补充这就是典型的生产者 - 消费者模式Lua 脚本是「快速校验 生产者」阻塞队列和线程池是「消费者」实现了高并发场景下的流量削峰。