下单时判断两个条件:
1、是否在优惠券领取时间内
2、库存是否充足
整体流程为如下图
基本下单功能代码:
@Override
@Transactional
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
return Result.fail("秒杀未开始!");
}
// 3.判断秒杀是否结束
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
return Result.fail("秒杀已结束!");
}
// 4.判断库存是否足够
if (voucher.getStock() < 1) {
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();
long orderId = redisIdWorker.nextId("order");
voucherorder.setId(orderId);
voucherorder.setVoucherId(voucherId);
Long userId = UserHolder.getUser().getId();
voucherorder.setUserId(userId);
save(voucherorder);
// 7.返回结果
return Result.ok(orderId);
}
遇到的问题:
超卖问题 高并发情况下 优惠券库存变为负数
在还未扣减库存时,另一个线程也开始查询
解决方法 加锁
悲观锁与乐观锁
使用乐观锁方案
判断查询得到的数据是否有被修改过
方案一:版本号法
方案二:CAS法 使用查询到的数据进行比较 替代版本
在扣减库存时增加条件
// 5.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).eq("stock",voucher.getStock())
.update();
结果库存没有用完
修改条件
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0)
.update();
但是依旧在频繁访问数据库 ,还需要进行进一步的优化
修改秒杀使得一个用户只能获得一个订单
查询过程中依旧有并发问题 使用悲观锁方案
要注意锁的释放时机 以及事务是否生效
将用户id作为锁
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
return Result.fail("秒杀未开始!");
}
// 3.判断秒杀是否结束
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
return Result.fail("秒杀已结束!");
}
// 4.判断库存是否足够
if (voucher.getStock() < 1) {
return Result.fail("库存不足!");
}
Long userId = UserHolder.getUser().getId();
synchronized(userId.toString().intern()) {
// 创建代理对象 解决事务问题
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
// 一人一单
Long userId = UserHolder.getUser().getId();
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 判断是否存在
if (count > 0) {
return Result.fail("用户已经抢过券了!");
}
// 5.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0)
.update();
if (!success) {
return Result.fail("库存不足!");
}
// 查询订单
// 6.生成订单
VoucherOrder voucherorder = new VoucherOrder();
long orderId = redisIdWorker.nextId("order");
voucherorder.setId(orderId);
voucherorder.setVoucherId(voucherId);
voucherorder.setUserId(userId);
save(voucherorder);
// 7.返回结果
return Result.ok(orderId);
}
解决了单机一人一单并发问题
但是集群模式下出现问题
多个JVM存在 有不同的锁 要让多个JVM使用同一把锁