分布式系统(1)Redisson实现分布式锁

一. 引入Redisson

1.1 Redisson介绍

Redisson在基于NIO的Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。
Redisson 可以提供分布式锁,延时队列,布隆过滤器等Redis高级功能。

1.2 Maven坐标

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.0</version>
        </dependency>

1.3 配置文件

@Configuration
public class RedissonConfig {
    
    
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
    
    
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://192.168.31.11:6379");
        RedissonClient redissonClient= Redisson.create(config);
        return redissonClient;
    }
}

二. 测试

1. 基本测试

@RestController
@RequestMapping("/testLock")
public class TestLockController {
    
    
    @Autowired
    private RedissonClient redisson;
    
    @RequestMapping("/test1")
    public String test1(){
    
    
      //1. 阻塞获取一把名为 locak1 的锁
        RLock lock = redisson.getLock("lock1");
        System.out.println("正在获取锁...");
        // 1.1 加锁 这里的加锁方式为 阻塞获取 直到获取锁才行
        lock.lock();
        // 1.2 自定义加锁时间
        // lock.lock(25,TimeUnit.SECONDS);
        // 1.3 尝试加锁 默认 最多 30秒
        // lock.tryLock();
        // ...
        System.out.println("获取到锁,执行业务...");
        try {
    
    
            // 业务代码
            Thread.sleep(10000);
        } catch (Exception e) {
    
    

        } finally {
    
    
            // 2. 解锁
            lock.unlock();
        }
        return "ok";
}

1.1 这个代码存在死锁么?

lock.lock() 是阻塞获取锁的,如果出现一种情况:在一个分布式系统下,获取到锁的服务器宕机了,没有及时的释放掉锁,那么获取锁的服务器会一直阻塞么

答案是 不会的,这里涉及到 redisson 实现的一个 机制:看门狗看门狗 赋予了 lock()方法的 默认的加锁时间是30秒,如果获取到锁的服务器发生宕机,那么锁在30秒后会自动释放掉,分布式系统非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。

那么又衍生出了另外一种情况:如果业务代码执行时间超过了30秒,这个锁被释放掉了,那么执行unlock() 释放锁 会发生什么?

这里有两种情况 第一种 lock() 方法没有自定义时间,那加锁时间就是30秒, 看门狗 会每隔 10秒自动续期到 30秒(其实就是把剩余时间从20秒改到30秒),直到 finally 代码块 执行释放锁,如果期间 服务器宕机了就不用说了,宕机以后看门狗也不会工作了,最多30秒有效时间

第二种情况,lock方法自定义了加锁时间,看门狗这个续时机制就不会触发,如果业务代码执行时间超过了自定义的时间后执行 unlock(),那么这个释放锁操作 是不会成功的,因为Redisson释放锁操作是个原子操作,他会比较redis的存放的锁数据 , key = key AND value = value(这里是用lua脚本实现的),这两个条件成立时才会执行成功,最后系统会抛出异常。

1.2 看门狗原理

RLock点击进去
在这里插入图片描述
继承了JUC下面的lock类,怪不得他的api方法和 lock这么像,我在一篇文章里也写到了 Lock 有兴趣的可以看一下ReentrantLock原理

他的实现类:
在这里插入图片描述
实现类里面无参 的lock方法:
在这里插入图片描述
这里的这个lock方法传三个参数 1. 时间 2 时间单位 3 是否可被打断

在这里插入图片描述

继续:
在这里插入图片描述
在这里插入图片描述
加锁的实现也是lua脚本:
在这里插入图片描述
返回上一层:
在这里插入图片描述
renewExpiration方法:
在这里插入图片描述
获取到锁后,会启动一个定时任务,定时任务 在 30/3 = 10 秒后 启动:
在这里插入图片描述

到这里就能看到 看门狗是通过定时任务实现来实现续时的
unlock方法这里我就不演示了

三. 进阶

3.1 读写锁

@RestController
@RequestMapping("/testLock")
public class TestLockController {
    
    
    @Autowired
    private RedissonClient redisson;

    @RequestMapping("/write")
    public String writeValue() {
    
    

        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        RLock writeLock = lock.writeLock();
        try {
    
    
            System.out.println("正在获取写锁...");
            writeLock.lock();
            System.out.println("写锁加锁成功..." );
            Thread.sleep(15000);
            //redisTemplate.opsForValue().set("writeValue",s);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            writeLock.unlock();
        }
        return "ok";
    }
    @RequestMapping("/read")
    public String readValue() {
    
    
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        RLock readLock = lock.readLock();
        try {
    
    
            System.out.println("正在获取读锁...");
            readLock.lock();
            System.out.println("读锁加锁成功...");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            readLock.unlock();
        }
        return "ok";
    }
}

3.2 读写锁规律

  1. 读 + 读 相当于无锁,并发读,只会在 reids中记录好,所有当前的读锁,他们都会同时加锁成功
  2. 写 + 读 等待写锁释放
  3. 写 + 写 阻塞方式
  4. 读 + 写 有读锁,写也需要等待
  5. 只要有写的存在,都必须等待

3.3 闭锁

有这样一个场景,晚上保安大叔来教室锁门,正好当时教室有五个学生,保安大叔需要等这五个学生离开教室,才能锁门。这种场景可以用到 闭锁

@RestController
@RequestMapping("/testLock")
public class TestLockController {
    
    
    @Autowired
    private RedissonClient redisson;
    /**
     * 锁门
     * @return
     * @throws InterruptedException
     */
    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
    
    
        RCountDownLatch door = redisson.getCountDownLatch("door");
        // 只有当 5变成 0的时候才能锁门
        door.trySetCount(5);
        door.await();//等待闭锁都完成

        return "锁门成功...";
    }
    /**
     *  离开教室
     * @param name
     * @return
     */
    @GetMapping("/outRoom/{name}")
    public String gogogo(@PathVariable("name") String name) {
    
    
        RCountDownLatch door = redisson.getCountDownLatch("door");
        // 计数器减一
        door.countDown();
        return  name + " 离开教室";
    }
}

3.4 信号量测试

/**
     * 车库停车
     * 3车位
     * @return
     */
    @GetMapping("/park")
    @ResponseBody
    public String park() {
    
    
        RSemaphore park = redisson.getSemaphore("park");
        // 占用一个车位
        boolean isOk = park.tryAcquire();

        return isOk + "";
    }

    @GetMapping("/out")
    @ResponseBody
    public String go() {
    
    
        RSemaphore park = redisson.getSemaphore("park");
        //释放一个车位
        park.release();
        return "ok";
    }

猜你喜欢

转载自blog.csdn.net/haiyanghan/article/details/112499063