一、超卖问题
业务要求:
实现一个对优惠券限时抢购的功能。
你可能会想,啥抢购啊,说白了不就是买东西吗,先判断一下是否在抢购时间,然后在买的时候判断一下是否还有库存不就行了。如果有,说明优惠券还没抢完,则让订单生效,并让库存减一;如果没有,说明已经抢完了,直接返回异常提示就好了。很容易就能得到下图中的流程:
这业务逻辑感觉好像没啥毛病,接着就可以根据这个流程编写代码了:
@Transactional //事务
public Result seckillVoucher(Long voucherId) {
//1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
//2.判断秒杀是否开始
LocalDateTime beginTime = voucher.getBeginTime();
if(LocalDateTime.now().isBefore(beginTime)){
//秒杀还未开始,返回提示信息
return Result.fail("秒杀还为开始!");
}
//3.判断秒杀是否结束
LocalDateTime endTime = voucher.getEndTime();
if(LocalDateTime.now().isAfter(endTime)){
//秒杀已经结束,返回提示信息
return Result.fail("秒杀已经结束!");
}
//4.判断库存是否充足
Integer stock = voucher.getStock();
if(stock<=0){
//优惠券已经抢完,没有库存了
return Result.fail("优惠券已被抢完!");
}
//5.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock-1")
.eq("voucher_id", voucherId).update();
if(!success){
//数据库更新失败
return Result.fail("优惠券已被抢完!");
}
//6.创建订单
VoucherOrder voucherOrder=new VoucherOrder();
//6.1.订单id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
//6.2.用户id
Long userId = UserHolder.getUser().getId();
voucherOrder.setUserId(userId);
//6.3.代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//7.返回订单id
return Result.ok(orderId);
}
好像已经把业务要求成功实现了,是不是万事大吉了?然而,现实场景中,由于秒杀的限时限量的特性,往往会吸引大量用户参与,就会给系统带来并发压力。让我们用jmeter模拟一下高并发场景下的运行情况:
设置200个线程发送秒杀请求,而数据库中存放了100张优惠券。正常情况下,最后应该有100个线程抢券成功,另一百个线程则返回异常信息,