分布式锁解决集群下一人一单超卖问题
由于本项目是专门学习Redis的,所以这里会使用Redis的setnx
指令实现分布式锁解决超卖问题
创建分布式锁:
public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;private static final String KEY_PREFIX = "lock:";public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(long timeoutSec) {String key = KEY_PREFIX + name;String value = Thread.currentThread().getId() + "";Boolean res = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(res);}@Overridepublic void unlock() {String key = KEY_PREFIX + name;stringRedisTemplate.delete(key);}
}
使用分布式锁:改造前面VoucherOrderServiceImpl中的代码,将之前使用sychronized
锁的地方,改成自己实现的分布式锁:
/*** 抢购秒杀券*/@Overridepublic Result seckillVoucher(Long voucherId) {// 1、查询秒杀券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2、判断秒杀券是否合法if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 秒杀券的开始时间在当前时间之后return Result.fail("秒杀尚未开始");}if (voucher.getEndTime().isBefore(LocalDateTime.now())) {// 秒杀券的结束时间在当前时间之前return Result.fail("秒杀已结束");}// 3、判断库存是否充足if (voucher.getStock() < 1) {return Result.fail("秒杀券已抢空");}Long userId = UserHolder.getUser().getId();// 去字符串常量池找字符串对象,使得加锁同一个对象// 先获取锁,再开启事务,事务结束后,才会释放锁String key = "order:" + userId;// 锁定范围是用户IDSimpleRedisLock lock = new SimpleRedisLock(key, stringRedisTemplate);boolean isLock = lock.tryLock(1200);if(!isLock){// 获取锁失败,返回错误或重试,但此时是同一个用户并发多个请求,应该返回错误return Result.fail("不允许重复下单");}// 获取锁成功try{// spring的事务是基于代理对象的,这里直接调用相当于this.xxx,并非代理对象,因此事务不会生效,所以要拿到代理对象IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}finally {lock.unlock();}}
重启程序,在postman中使用同一个用户的token发送两次请求,可以发现只有有一个用户获取锁成功。