由于本项目是专门学习Redis的,所以这里会使用Redis的setnx
指令实现分布式锁解决超卖问题

创建分布式锁:
java
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;
}
@Override
public 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);
}
@Override
public void unlock() {
String key = KEY_PREFIX + name;
stringRedisTemplate.delete(key);
}
}
使用分布式锁 :改造前面VoucherOrderServiceImpl中的代码,将之前使用sychronized
锁的地方,改成自己实现的分布式锁:
java
/**
* 抢购秒杀券
*/
@Override
public 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;
// 锁定范围是用户ID
SimpleRedisLock 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发送两次请求,可以发现只有有一个用户获取锁成功。
