一:
利用SetEX+设置过期时间进行分布式锁的实践:
public Result order(Long voucherId) {
SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId);
LocalDateTime beginTime = seckillVoucher.getBeginTime();
LocalDateTime endTime = seckillVoucher.getEndTime();
//现在时间在秒杀活动之前
if (beginTime.isAfter(LocalDateTime.now())) {
return Result.fail("秒杀活动还没开始!");
}
//现在时间在秒杀活动之后
if (endTime.isBefore(LocalDateTime.now())) {
return Result.fail("秒杀活动已经结束!");
}
//判断库存是否充足
if (seckillVoucher.getStock()<1){
return Result.fail("库存不足");
}
Long userId = UserHolder.getUser().getId();
System.out.println("====>"+userId);
//TODO 先释放锁,然后再提交事务
//和 spring代理对象不同
Lock voucherOrder = new Lock("voucherOrder", stringRedisTemplate);
boolean isLock = voucherOrder.tryLock(1200);
//判断是否获取锁成功
if (!isLock) {
//重试或返回错误信息
return Result.fail("不允许重复下单");
}
try {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.getResult(voucherId);
} catch (IllegalStateException e) {
throw new RuntimeException(e);
}finally {
voucherOrder.unlock();
}
}
@Transactional(rollbackFor = Exception.class)
public Result getResult(Long voucherId) {
Long userId = UserHolder.getUser().getId();
Integer count = query().eq("user_id", UserHolder.getUser().getId()).eq("voucher_id", voucherId).count();
System.out.println(count);
//判断用户是否下过单
if (count > 0) {
return Result.fail("您已经下过订单");
}
//TODO 库存减1
// 在执行减库存操作时,判断是否和查询时的库存一致,一致才可修改
boolean success = iSeckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherId).
gt("stock", 0).update();
if (!success) {
return Result.fail("库存不足,秒杀失败!");
}
//TODO 创建订单
VoucherOrder order = new VoucherOrder();
long orderID = idWorker.nextId(RedisConstants.SECKILL_STOCK_KEY);
order.setVoucherId(voucherId);
order.setId(orderID);
order.setUserId(userId);
save(order);
//TODO 返回订单ID
return Result.ok(orderID);
}
当检查完之后如果此时业务发生阻塞。线程二开始执行,线程一唤醒后继续删除,仍然会删除线程二的锁。所以要保证查询与删除的原子性,所以我们可以使用lua脚本
实现代码:
local id =redis.call('get',KEYS[1])
if(id==ARGV[1]) then
--释放锁
redis.call('del',KEYS[1])
end
return 0
@Override
public void unlock() {
//获取线程标识
stringRedisTemplate.execute(UN_LOCK_SCRIPT, Collections.singletonList(KEY_PREFIX+name),
ID_PREFIX+Thread.currentThread().getId());
}
}
使用redisson
引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.5</version>
</dependency>
配置redisson
@Configuration
public class RedisConfig {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
//使用单点地址。集群地址使用config.useClusterServers()
config.useSingleServer().setAddress("redis://119.29.57.85:6379").setPassword("redis777");
return Redisson.create(config);
}
}
@Autowrite
private RedissonClient redissionclient;
public Result order(Long voucherId) {
SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId);
LocalDateTime beginTime = seckillVoucher.getBeginTime();
LocalDateTime endTime = seckillVoucher.getEndTime();
//现在时间在秒杀活动之前
if (beginTime.isAfter(LocalDateTime.now())) {
return Result.fail("秒杀活动还没开始!");
}
//现在时间在秒杀活动之后
if (endTime.isBefore(LocalDateTime.now())) {
return Result.fail("秒杀活动已经结束!");
}
//判断库存是否充足
if (seckillVoucher.getStock()<1){
return Result.fail("库存不足");
}
Long userId = UserHolder.getUser().getId();
System.out.println("====>"+userId);
//TODO 先释放锁,然后再提交事务
//和 spring代理对象不同
//Lock voucherOrder = new Lock("voucherOrder", stringRedisTemplate);
RLock lock = redissonClient.getLock("lock:order:" + userId);
boolean isLock = lock.tryLock();
//boolean isLock = voucherOrder.tryLock(1200);
//判断是否获取锁成功
if (!isLock) {
//重试或返回错误信息
return Result.fail("不允许重复下单");
}
try {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.getResult(voucherId);
} catch (IllegalStateException e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}