Loading

redis分布式锁

所见即是我,至于你怎么看我,无关紧要,也没必要。

基本原理

本地锁只能锁住一台机器上的单例线程,在分布式系统中,有多少台机器,就会放进多少个查询线程。因此,在分布式系统中,需要使用分布式锁。

分布式基本原理:所有的机器去同一个地方“占坑”,如果占到,就执行逻辑,否则就必须等待,直到释放锁。“占坑”可以去redis,可以去数据库,可以在所有机器都能访问的地方,等待则使用自旋的方式。

分布式锁演进-阶段一

// 1.占分布式锁。即在redis里面进行“占坑”
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1");
// 2.拿到“占坑”锁的机器线程去执行业务,其他的线程则进入等待
if (lock) {
// 拿到锁的线程进入开始执行业务
......
// 业务执行完成,删除redis的锁,让其他等待线程可以拿锁执行业务
redisTemplate.delete("lock");
} else {
// 没有拿到锁的线程,开始进行自旋等待
// 休眠100ms重新调用本方法
}

可能出现的问题:

拿到锁的线程在执行业务的过程中抛了异常或者机器宕机,后面执行锁的业务没有执行,redis中的锁一直没有删除,成了死锁,后续所有等待的线程都拿不到锁执行不了业务了。

解决方法:

设置锁的过期时间,即使没有删除,也会自动删除。

分布式锁演进-阶段二

// 1.占分布式锁。即在redis里面进行“占坑”
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1");
// 2.拿到“占坑”锁的机器线程去执行业务,其他的线程则进入等待
if (lock) {
// 设置过期时间
redisTemplate.expire("lock", 30, Ti,eUnit.SECONDS);
// 拿到锁的线程进入开始执行业务
......
// 业务执行完成,删除redis的锁,让其他等待线程可以拿锁执行业务
redisTemplate.delete("lock");
} else {
// 没有拿到锁的线程,开始进行自旋等待
// 休眠100ms重新调用本方法
}

可能出现的问题:

setnx设置好,去设置锁过期时间时,又宕机了,又会出现死锁问题。

解决方法:

设置过期时间和“占坑”是原子的,redis支持使用setnx ex命令。

分布式锁演进-阶段三

// 1.占分布式锁。即在redis里面进行“占坑”
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1", 300, TimeUnit.SECONDS);
// 2.拿到“占坑”锁的机器线程去执行业务,其他的线程则进入等待
if (lock) {
// 拿到锁的线程进入开始执行业务
......
// 业务执行完成,删除redis的锁,让其他等待线程可以拿锁执行业务
redisTemplate.delete("lock");
} else {
// 没有拿到锁的线程,开始进行自旋等待
// 休眠100ms重新调用本方法
}

可能出现的问题:

删除别人的锁。由于执行业务的时间过长,锁在途中自己过期了,过期之后下一个线程就可以拿锁开始执行业务,这时候,上一个线程业务执行完了,此时删除锁,删除的就是下一个线程的锁。

解决方法:

为每一把锁执行uuid,删除的时候匹配的锁才能删除。

分布式锁演进-阶段四

可能出现的问题:

如果判断正好是当前锁的之后,正要执行下一步删除锁操作时,锁自动过期了,别的线程已经设置了新的值,那么删除的还是别人的锁。

解决方法:

比对锁与删除锁必须保证是原子操作。使用官方提供的redis + lua脚本完成

参考:http://redis.cn/commands/set.html

分布式锁演进-终

保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
   String uuid = UUID.random().toString();
   ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
   Boolean lock = ops.setIfAbsent("lock", uuid, 500, TimeUnit.SECONDS);
   if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
       String lockValue = ops.get("lock");
       // get和delete原子操作
       String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
           "   return redis.call(\"del\",KEYS[1])\n" +
           "else\n" +
           "   return 0\n" +
           "end";
       stringRedisTemplate.execute(
           new DefaultRedisScript<Long>(script, Long.class), // 脚本和返回类型
           Arrays.asList("lock"), // 参数
           lockValue); // 参数值,锁的值
       return categoriesDb;
  }else {
       try {
           Thread.sleep(100);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       // 睡眠0.1s后,重新调用
       return getCatalogJsonDbWithRedisLock();
  }
}
 
posted @ 2022-10-11 09:35  你比从前快乐;  阅读(171)  评论(0编辑  收藏  举报