美文网首页Redis
Redis缓存之(一)分布式锁

Redis缓存之(一)分布式锁

作者: 与乐为乐 | 来源:发表于2020-06-29 00:33 被阅读0次

    一、实现方式一:redisTemplate

    1. 引入redis启动器starter
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    2. 配置yml
    spring
      redis:
        host: 127.0.0.1
        port: 6379
    
    3. 代码实现
    @Autowired
    StringRedisTemplate redisTemplate;
    
    /**
     * 使用分布式锁来实现多个服务共享同一缓存中的数据
     * (1)设置读写锁,失败则表明其他线程先于该线程获取到了锁,则执行自旋,成功则表明获取到了锁
     * (2)获取锁成功,查询数据库,获取分类数据
     * (3)释放锁
     * @return
     */
    
    public Map<String, Object> getRedisLock() {
        String uuid= UUID.randomUUID().toString();
    
     Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
    
    if(lock) {
        // 加锁成功……执行业务
        try{
          // ……
        } finally {
            //确保一定会释放锁
            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(script,Long.class),Arrays.asList("lock"),uuid);
        }
    
    } else {
      // 加锁失败……休眠100ms 重试
      // 自旋,调用自己
      getRedisLock() 
    }
    
    
    }
    
    
    

    一、实现方式二:Redisson(官方推荐使用)

    Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)

    Ridisson文档

    1. 引入redission依赖
    <dependency>
       <groupId>org.redisson</groupId>
       <artifactId>redisson</artifactId>
       <version>3.12.5</version>
    </dependency> 
    
    2. 创建“MyRedisConfig” 配置类
    @Configuration
    public class MyRedisConfig {
    
        @Bean(destroyMethod="shutdown")
        public RedissonClient redisson() throws IOException {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://127.0.0.1:6379");
            RedissonClient redisson = Redisson.create(config);
            return redisson;
        }
    
    }
    
    3. 可重入锁(Reentrant Lock)
    • 所有的锁都应该设置为可重入锁,来防止死锁。
    • ps: 方法A调用方法B,两个方法都加的是1号锁,如果是可重入锁,A方法获取到锁后执行,执行到B方法后,B方法一看已经加了1号锁就直接执行B方法,执行完后,A方法释放锁。
    • 如果是不可重入锁,A方法获取到1号锁后,调用B方法,B方法也需要获取到1号锁,造成死锁
    3.1. 门狗原理

    设想一种情况,一个请求线程在执行业务方法的时候,突然发生了中断,此时没有来得及执行释放锁操作,那么同时等待的另外一个线程是否会发生死锁?

    文档原文引用:
    大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改
    wiki

    @Autowired
    RedissonClient redisson;
    
    @GetMapping("/hello")
    @ResponseBody
    public String hello(){
        //1.获取一把锁,只要名字一样,就是同一把锁
        RLock lock = redisson.getLock("my-lock");
    
         // 2.加锁
            lock.lock();  // 阻塞式等待,直到能拿到锁,默认加锁时间30s
          // 1)锁到自动续期,如果业务超长,运行期间自动给锁续上新到30s。不用担心业务时间过长后,锁自动过期被删掉
          // 2)加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除,没有死锁问题。
        try {
         // 执行业务代码……
        }finally {
          // 3.解锁
            lock.unlock();
        }
        return "hello";
    }
    
    3.2. 小结:redisson的lock特点
    1. 阻塞式等待。默认的锁的时间是30s。

    2. 锁定的制动续期,如果业务超长,运行期间会自动给锁续上新的30s,无需担心业务时间长,锁自动被删除的问题。

    3. 加锁的业务只要能够运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除。

    3.3. 扩展部分
    1. 关于续期周期,只要锁占领成功,就会自动启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动再次续期,续成30s。这个10s中是根据( internalLockLeasTime)/3得到的
    4. Redisson-读写锁
    • 保证一定能够读取到最新的数据,修改期间,写锁是一个排他锁(互斥锁),读锁是一个共享锁,写锁没释放读就必须等待。
        @GetMapping("/write")
        @ResponseBody
        public String writeValue(){
            RReadWriteLock writeLock=redisson.getReadWriteLock("rw-loc");
            String uuid = null;
            RLock lock = writeLock.writeLock();
            lock.lock();
            try {
                log.info("写锁加锁成功");
                uuid = UUID.randomUUID().toString();
                redisTemplate.opsForValue().set("writeValue",uuid);
            }finally {
                lock.unlock();
                log.info("写锁释放");
    
            }
            return uuid;
        }
    
        @GetMapping("/read")
        @ResponseBody
        public String redValue(){
            String uuid = null;
            RReadWriteLock readLock=redisson.getReadWriteLock("rw-loc");
            RLock lock = readLock.readLock();
            lock.lock();
            try {
                log.info("读锁加锁成功");
                 uuid = redisTemplate.opsForValue().get("writeValue");
            }finally {
                lock.unlock();
                log.info("读锁释放");
    
            }
            return uuid;
        }
    }
    
    4.1. 小结

    读+读:相当于无锁,并发读,只会在redis中记录,所有当前的读锁,都会同时加锁成功
    写+读:等待写锁释放;
    写+写:阻塞方式
    读+写:写锁等待读锁释放,才能加锁

    • 所以只要存在写操作,不论前面是或后面执行的是读或写操作,都会阻塞。

    相关文章

      网友评论

        本文标题:Redis缓存之(一)分布式锁

        本文链接:https://www.haomeiwen.com/subject/ozlnfktx.html