1.问题描述
由于高并发,会出现超卖问题,解决办法就是
悲观锁与乐观锁,乐观锁有版本号和CAS两种解决办法,采用CAS解决
2.代码
主要在这个地方,在更新减库存的时候,要直接判断是不是库存是否大于0。
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker; //订单id生成器 全局唯一id
@Transactional()
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠劵
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 如果查不到结果(即没有匹配的记录),这个方法会直接返回 null。
if (voucher == null) {
return Result.fail("优惠券不存在");
}
// 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.扣减库存
/*
UPDATE seckill_voucher
SET stock = stock - 1
WHERE voucher_id = #{voucherId}
*/
boolean success = seckillVoucherService.update()
.setSql("stock = stock-1")
.eq("voucher_id", voucherId)
.gt("stock", 0)
.update() ;
// 秒杀 悲观锁和乐观锁(版本号法(不会出现ABA问题)和CAS法(可能出现ABA问题)),秒杀不会发生ABA问题,因为库存只会减,不会增
if(!success)
{
return Result.fail("库存不足");
}
// 6.创建订单 订单表
VoucherOrder voucherOrder = new VoucherOrder();
// 6.1 订单id
long voucherOrderId = redisIdWorker.nextId("voucherOrder");//传入的是前缀
voucherOrder.setId(voucherOrderId);
// 6.2 用户id 拦截器获取
UserDTO user = UserHolder.getUser();
if(user==null)
{
return Result.fail("用户没有登陆,无法抢购优惠劵");
} // 也可以在拦截器中实现
voucherOrder.setUserId(user.getId());
// 6.3 代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
// 7.返回订单id
return Result.ok(voucherOrderId);
}
}
3.jmeter测试bug
很奇怪,为什么不是50%,数据库100个卷都已经卖出去了。
原来是要清理,点击清理按钮即可,再加上响应断言