分布式缓存(三) 分布式锁的原理与使用

本地锁只能锁住当前进程,所以我们需要分布式锁

Redis:  SET key value [EX] [NX] [XX]

其中:EX:设置过期时间

          NX:Only set the key if it does not already exist. 当redis中没有这个key的时候才去设置这个key

在docker中模拟:

向5个客户端发送(docker环境): docker exec -it redis redis -cli   以命令行的方式进入redis会话中

向5个客户端发送:set lock haha NX   这5个去抢占,只有一个能成功

public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock(){
   // 1.占分布式锁,去redis占坑   setIfAbsent相当于set lock 111 NX
   Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111");
   if(lock){
     // 加锁成功,执行业务
     Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
     redisTemplate.delete("lock");
     return dataFromDb; 
   } else {
     // 加锁失败,重试  休眠100ms
     return getCatelogJsonFromDbWithRedisLock(); // 自旋的方式
   } 
}

存在问题:如果没有执行到删锁逻辑,就变成了死锁;

考虑:设置过期时间:

public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock(){
   // 1.占分布式锁,去redis占坑   setIfAbsent相当于set lock 111 NX
   Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111");
   if(lock){
     // 加锁成功,执行业务
     // 设置过期时间
     redisTemplate.expire("lock",30,TimeUnit.SECONDS)
     Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
     redisTemplate.delete("lock");
     return dataFromDb;
   } else {
     // 加锁失败,重试  休眠100ms
     return getCatelogJsonFromDbWithRedisLock(); // 自旋的方式
   }
}

还存在问题: 如果加锁和设置过期时间之间突然断电,设置过期时间的代码执行不到,导致死锁

解决:将加锁和设置过期时间处理为原子操作  set lock 111 EX 300 NX

public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock(){
   // 1.占分布式锁,去redis占坑   setIfAbsent相当于set lock 111 NX
   Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",300,TimeUnit.SECONDS); // 设置加锁和过期时间1行代码完成
   if(lock){
     // 加锁成功,执行业务
     Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
     redisTemplate.delete("lock");
     return dataFromDb;
   } else {
     // 加锁失败,重试  休眠100ms
     return getCatelogJsonFromDbWithRedisLock(); // 自旋的方式
   }
}

还存在问题:如果业务的执行时间,大于设置的锁的过期时间,这样会导致锁过期后,别的锁进来,删除的是别人的锁

解决:占锁的时候,指定一个UUID,每个人的都不一样

public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock(){
   // 1.占分布式锁,去redis占坑   setIfAbsent相当于set lock 111 NX
   String uuid = UUID.randomUUID().toString();
   Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS); // 设置加锁和过期时间1行代码完成
   if(lock){
     // 加锁成功,执行业务
     Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
     String lockValue = redisTemplate.opsForValue().get("lock");
     if(uuid.equals(lockValue)){
       redisTemplate.delete("lock");
     }
     return dataFromDb;
   } else {
     // 加锁失败,重试  休眠100ms
     return getCatelogJsonFromDbWithRedisLock(); // 自旋的方式
   }
}

还可能存在问题: String lockValue = redisTemplate.opsForValue().get("lock"); 耗时比较长,导致uuid和缓存中的虽然一样,满足(uuid.equals(lockValue),但是实际上redis中的lock可能已经设置为新的值了

解决:获取值对比 + 对比成功删除 ==> 需要为原子操作  使用lua脚本解锁

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock(){
   // 1.占分布式锁,去redis占坑   setIfAbsent相当于set lock 111 NX
   String uuid = UUID.randomUUID().toString();
   Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS); // 设置加锁和过期时间1行代码完成
   if(lock){
     // 加锁成功,执行业务
     Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
     String script = "if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end";
     // 删除锁(原子操作)
     redisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class),Arrays.asList("lock"),uuid)
     return dataFromDb;
   } else {
     // 加锁失败,重试
     return getCatelogJsonFromDbWithRedisLock(); // 自旋的方式
   }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值