优惠券秒杀业务分析
2026/5/4 23:52:21 网站建设 项目流程

文章目录

  • 业务分析
    • 问题1:高并发下出现超售
    • 问题2:一个用户抢购多张
      • (1)使用同步锁 synchronized
      • (2)事务失效
    • 最终代码结构

业务分析

秒杀业务的核心难点在于应对高并发带来的数据一致性问题,主要聚焦两个矛盾:

  • 限定数量,防止超售:库存扣减需要保证原子性,避免并发下卖出超过实际库存。
  • 一人一单:同一个用户 ID 只能抢购一次,保证公平。

问题1:高并发下出现超售

多个线程同时读取库存为 1,都判断“充足”,然后各自扣减,结果库存变成负数。

  • 解决方案:CAS 乐观锁,在更新库存时,不依赖锁,而是增加一个版本条件,仅当库存大于 0 时才扣减。
booleansuccess=seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id",voucherId).gt("stock",0)// CAS 条件:库存 > 0.update();

问题2:一个用户抢购多张

(1)使用同步锁 synchronized

给“查询是否已购买”和“创建订单”整个逻辑加锁。

synchronized(userId.toString().intern()){returncreateVoucherOrder(voucherId);}

所有用户争夺同一把锁,锁粒度太粗。→ 改为 只对当前用户 ID 加锁,即 synchronized (userId.toString().intern())。

intern() 保证相同字符串对象唯一,避免不同线程使用不同 String 对象导致锁失效。

(2)事务失效

上述代码看似可行,但@Transactional 会失效,原因是 Spring AOP 的代理机制。createVoucherOrder 方法标记了 @Transactional,期望在事务中执行。然而我们通过 this.createVoucherOrder 直接调用,跳过了 Spring 生成的代理对象。

Spring 的事务管理基于 AOP 动态代理(JDK/CGLIB)。容器注入的是代理对象,代理对象会在调用目标方法前后处理事务(开启、提交/回滚)。如果我们拿到的是原始对象(this)并直接调用,代理根本没机会插手,事务注解自然失效。

解决方案:暴露代理对象

  • 在主方法中通过 AopContext.currentProxy() 获取当前类的代理对象。
  • 通过代理对象调用 createVoucherOrder。

必须先在启动类或配置上启用 @EnableAspectJAutoProxy(exposeProxy = true),允许暴露代理。

// 主方法publicResultseckillVoucher(LongvoucherId){// ...synchronized(userId.toString().intern()){IVoucherOrderServiceproxy=(IVoucherOrderService)AopContext.currentProxy();returnproxy.createVoucherOrder(voucherId);}}

最终代码结构

@OverridepublicResultseckillVoucher(LongvoucherId){//查询优惠券SeckillVouchervoucher=seckillVoucherService.getById(voucherId);//判断秒杀是否开始if(voucher.getBeginTime().isAfter(LocalDateTime.now())){//秒杀尚未开始returnResult.fail("秒杀尚未开始");}if(voucher.getEndTime().isBefore(LocalDateTime.now())){//秒杀已经结束returnResult.fail("秒杀已经结束");}//判断库存是否充足if(voucher.getStock()<1){//库存不足returnResult.fail("库存不足");}LonguserId=UserHolder.getUser().getId();//仅限单体应用使用synchronized(userId.toString().intern()){//实现获取代理对象 比较复杂IVoucherOrderServiceproxy=(IVoucherOrderService)AopContext.currentProxy();returncreateVoucherOrder(voucherId);}
@TransactionalpublicResultcreateVoucherOrder(LongvoucherId){// 5. 一人一单LonguserId=UserHolder.getUser().getId();// 5.1. 查询订单intcount=query().eq("user_id",userId).eq("voucher_id",voucherId).count();// 5.2. 判断是否存在if(count>0){// 用户已经购买过了returnResult.fail("用户已经购买过一次!");}// 6. 扣减库存booleansuccess=seckillVoucherService.update().setSql("stock = stock - 1")// set stock = stock - 1.eq("voucher_id",voucherId).gt("stock",0)// where id = ? and stock > 0.update();if(!success){// 扣减失败returnResult.fail("库存不足!");}// 7. 创建订单VoucherOrdervoucherOrder=newVoucherOrder();// 7.1. 订单idlongorderId=redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 7.2. 用户idvoucherOrder.setUserId(userId);// 7.3. 代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 7. 返回订单idreturnResult.ok(orderId);}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询