Redis

Redis缓存之(一)分布式锁

2020-06-29  本文已影响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)
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中记录,所有当前的读锁,都会同时加锁成功
写+读:等待写锁释放;
写+写:阻塞方式
读+写:写锁等待读锁释放,才能加锁

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

猜你喜欢

热点阅读