优惠券秒杀:
RedisIdWorker类:
/**
* 开始的时间戳,2024-2-5
*/
private final static long BEGIN_TIMESTAMP = 1707091200L;
/**
* 序列号位数
*/
private final static int COUNT_BITS=32;
@Autowired
StringRedisTemplate stringRedisTemplate;
public long nextId(String keyPrefix){
//生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond-BEGIN_TIMESTAMP;
//生成序列号
//获取当天日期
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
//拼接并返回
return timestamp<<COUNT_BITS | count;
}
业务类:VoucherOrderServiceImpl
@Autowired
SeckillVoucherServiceImpl service;
@Autowired
RedisIdWorker worker;
@Override
@Transactional
public Result seckillVoucher(Long voucherId) {
//查询优惠券
SeckillVoucher byId = service.getById(voucherId);
//判断秒杀时间
if (byId.getBeginTime().isAfter(LocalDateTime.now())) {
//秒杀尚未开始,返回错误信息
return Result.fail("秒杀尚未开始");
}
if(byId.getEndTime().isBefore(LocalDateTime.now())){
//秒杀已结束,返回错误信息
return Result.fail("秒杀已结束");
}
//判断库存是否充足
if (byId.getStock()<1) {
//库存不足
return Result.fail("库存不足!");
}
//扣减库存
boolean isSuccess = service.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();
if(!isSuccess){
//扣减失败
return Result.fail("库存不足");
}
//创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//订单ID,使用RedisIdWorker
long orderId = worker.nextId("order");
voucherOrder.setId(orderId);
//用户ID,用ThreadLocal中取出
voucherOrder.setUserId(UserHolder.getUser().getId());
//优惠券ID
voucherOrder.setVoucherId(voucherId);
boolean save = save(voucherOrder);
//返回订单
return Result.ok(orderId);
}
订单超卖问题:
异常问题:多个线程抢购,库存有可能出现负数
解决方案一:乐观锁,添加版本号:添加version字段
简化乐观锁:修改时和查询时作stock比较:cas(Compare And Set)缺点:买不了多少
改进:判断库存大于0就减
一人一单:
用悲观锁:
1、获取锁,锁住userId
2、提交事务:
事务提交只能用代理对象(proxy),不能用目标对象(this),
(1)、添加依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
(2)、在启动类上添加注解:
@EnableAspectJAutoProxy(exposeProxy = true)
(3)、业务类Impl代码:
Long userId = UserHolder.getUser().getId();
synchronized (userId.toString().intern()){
IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
(4)、createVoucher方法:
@Transactional
public Result createVoucherOrder(Long voucherId) {
Long userId = UserHolder.getUser().getId();
//一人一单
Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
if(count>0){
//不能再抢优惠券了
return Result.fail("用户已购买,不能重复");
}
//扣减库存,乐观锁
boolean isSuccess = service.update().setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.gt("stock",0)
.update();
if(!isSuccess){
//扣减失败
return Result.fail("库存不足");
}
//创建订单
VoucherOrder voucherOrder = new VoucherOrder();
//订单ID,使用RedisIdWorker
long orderId = worker.nextId("order");
voucherOrder.setId(orderId);
//用户ID,用ThreadLocal中取出
voucherOrder.setUserId(userId);
//优惠券ID
voucherOrder.setVoucherId(voucherId);
boolean save = save(voucherOrder);
//返回订单
return Result.ok(orderId);
}
3、释放锁