Redis进阶:分布式锁问题

分布式锁问题

  • 1. 分布式锁问题
    • 1.1 问题介绍
    • 1.2 解决方案
      • 1.2.1 分布式锁主流的实现方案
      • 1.2.2 使用Redis实现分布式锁
      • 1.2.3 分布式锁需要满足的四个条件
    • 1.3 实现分布式锁

1. 分布式锁问题

1.1 问题介绍

  • 单机单体中的锁机制在分布式集群系统中失效;
  • 单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问;

1.2 解决方案

1.2.1 分布式锁主流的实现方案

  • 基于数据库实现分布式锁;
  • 基于缓存(Redis等),性能最高
  • 基于Zookeeper,可靠性最高;

1.2.2 使用Redis实现分布式锁

Redis进阶:分布式锁问题_第1张图片

  • 使用set命令添加一个mutex key,同时为该key设置过时时间,并要求该key存在时无法更新数据
  • 通过set命令设置的mutex key即为分布式锁,在该key过时或被删除以前,此key无法被其他请求获取到;
  • 具体可通过set mutex_key uuid nx ex 过期时间进行加锁,其中mutex_key为锁名,value为一唯一值,过期时间以秒为单位;
  • 可通过del mutex_key 手动释放锁,但缺乏原子性,可通过lua脚本进行删除保证操作原子性
    Redis进阶:分布式锁问题_第2张图片
  • set命令中使用UUID做value是为了解决该问题:当前服务器可能释放其他服务器的锁,而自身的锁也可能被其他服务器释放
  • UUID是一个唯一值,用于标识不同的服务器;

Redis进阶:分布式锁问题_第3张图片
Redis进阶:分布式锁问题_第4张图片

1.2.3 分布式锁需要满足的四个条件

  • 互斥性:在任意时刻,只有一个客户端能持有锁;
  • 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁;
  • 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了;
  • 加锁和解锁必须具有原子性

1.3 实现分布式锁

  • 基于SpringBoot框架,常用配置参考https://springdoc.cn/spring-boot/application-properties.html#application-properties.data.spring.data.redis.cluster.nodes;
  • 代码:
    // 分布式锁测试
    @RequestMapping("/lockTest")
    public void lockTest(){
        String uuid= UUID.randomUUID().toString();
        // 获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
        // 成功获取锁
        if (lock){
            // 逻辑业务
            Object value = redisTemplate.opsForValue().get("num");
            if(StringUtils.isEmpty(value)){
                return;
            }
            Long num = Long.parseLong(value+"");
            redisTemplate.opsForValue().set("num",num+1);
            // 手动释放锁
            String lockVal = (String) redisTemplate.opsForValue().get("lock");
            if (lockVal.equals(uuid)) {
                redisTemplate.delete("lock");
            }
            // 定义lua脚本
            String luaScript="if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1])" +
                    "else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(luaScript);
            redisScript.setResultType(Long.class);
            // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值
            redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
        }else{
            // 等待一会再重新尝试获取锁
            try {
                Thread.sleep(100);
                lockTest();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

你可能感兴趣的:(Redis,Java开发,redis,分布式,java,缓存,数据库)