Redisson分布式锁框架

概述

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

Redisson底层采用的是Netty 框架。

githubhttps://github.com/redisson/redisson
学习文档https://github.com/redisson/redisson/wiki

使用

使用Redisson作为所有分布式锁,分布式对象等功能框架。

依赖:

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

配置类:
注意这个是单节点的

@Configuration
public class MyRedissonConfig {
    /**
     * 所有对redisson的使用都是通过RedissonClient对象
     * @return
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient(){
        //创建配置
        Config config = new Config();
        //单节点的配置 Redis url should start with redis:// or rediss:// (for SSL connection)
        config.useSingleServer().setAddress("redis://192.168.59.131:6379");
        //根据config创建出redissonClient示例
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

其他模式参考官网:
在这里插入图片描述
测试:

    @Autowired
    private RedissonClient redissonClient;

    @Test
    public void redissonTest(){
        System.out.println(redissonClient);
    }

体会Redisson的分布式锁

    @ResponseBody
    @GetMapping("/hello")
    public String hello(){
    
    
        //获取一把锁,只要锁的名字一样,就是同一把锁
        RLock myLock = redissonClient.getLock("myLock");
        //加锁
        myLock.lock();//阻塞式等待,以前自己写是,抢不到锁就自旋重试
        try{
    
    
            System.out.println("加锁成功,执行业务..."+Thread.currentThread().getName());
            Thread.sleep(9000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //解锁
            //假设解锁代码没有执行,redisson会不会出现死锁
            System.out.println("释放锁..."+Thread.currentThread().getName());
            myLock.unlock();
        }
        return "hello";
    }

测试:

在这里插入图片描述
在这里插入图片描述

如果启动当前微服务启动两个,第一个在执行业务逻辑的过程中,宕机了,没有释放锁,会出现死锁吗?
测试,当前微服务开两个,端口10000和端口10001
在这里插入图片描述
先访问端口10000的接口,在访问端口10001的接口,10000的接口在执行业务逻辑的时候,停了它的服务,模拟宕机。
发现过一会,10001一样能获取到锁,执行业务逻辑,并没有出现死锁。

redisson的强大:
1.锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长,锁自动过期被删掉
2.加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁也会默认会在30s后自动删除

看门狗机制

myLock.lock(10, TimeUnit.SECONDS);如果指定了锁过期时间,锁到时间后,如果业务还没执行完,是不会自动续期的,这个时候别的线程就能抢锁了,等到线程执行完业务,去删除锁了时候,就会报错,不能删别的线程的锁。

原理:看门狗机制
1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们制定的时间
2、如果我们未指定锁的超时时间,就使用lockWatchdogTimeout=30*1000【看门狗默认时间】,做为超时时间。只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动的再次续期,续成30秒,internalLockLeaseTime[看门狗时间]/ 3, 10s

最佳实战
myLock.lock(30, TimeUnit.SECONDS);省掉续期的操作
业务超时怎么办?业务如果能超时30s,那就废了

读写锁

改数据上写锁,读数据上读锁。
写锁:只希望同时只有一个线程操作
读取:希望可以多个线程操作
读锁和写锁是互斥的,也就是说,写的时候不能读,读的时候不能写。

读的时候希望多线程读,那不上锁,不就完事了吗?
不行的,如果不上读锁,意味着,数据在写的时候,也能被读取,可能会出现多次读取数据不一致的问题。上了读锁,数据在写的时候,不能读取数据,就不会出现数据不一致的问题。

    @ResponseBody
    @GetMapping("/write")
    public String writeValue(){
    
    
        //创建一个读写锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        //获取写锁
        RLock writeLock = readWriteLock.writeLock();

        String uuid="";
        try {
    
    
            //改数据上写锁
            writeLock.lock();
			//假设业务就是往redis里写数据
            uuid= UUID.randomUUID().toString();
            Thread.sleep(15000);
            stringRedisTemplate.opsForValue().set("writeValue",uuid);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            //解锁
            writeLock.unlock();
        }
        return uuid;
    }
    @ResponseBody
    @GetMapping("/read")
    public String readValue(){
    
    
        //创建一个读写锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        //获取读锁
        RLock readLock = readWriteLock.readLock();
        try {
    
    
            //上读锁
            readLock.lock();
            //假设业务就是从redis里读取数据
            String writeValue = stringRedisTemplate.opsForValue().get("writeValue");
            System.out.println(writeValue);
            return writeValue;
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            //解锁
            readLock.unlock();
        }
        return null;
    }

测试:
在这里插入图片描述
等到数据写完,就能读取了

补充:
写+读:先写再读,读锁等待写锁释放
写+写:一个写完,另个一个才能写。阻塞方式
读+写:先读在写,写锁等待读锁释放
读+读:并发读。只会在redis中记录好,所有的当前的读锁,它们都会同时加锁成功

闭锁(CountDownLatch)

闭锁(CountDownLatch):多线程调度的时候,可能会用,比如,十个线程任务都做完了,才算完了

场景:放假,锁门
假设5个班级,只有5个班级得人全走完了,才能锁大门

    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
    
    
        //闭锁
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        //总量设置为5
        door.trySetCount(5);
        door.await();//等待闭锁完成
        return "放假了...";
    }
    @GetMapping("/go/{id}")
    @ResponseBody
    public String go(@PathVariable("id")Long id){
    
    
        //闭锁
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.countDown();//计数减一
        return id+"班的人都走了...";
    }

测试:
访问闭锁接口/lockDoor,初始化了闭锁,闭锁进入阻塞
在这里插入图片描述
在这里插入图片描述
访问/go/{id}接口,消费闭锁
在这里插入图片描述
在这里插入图片描述
消费完闭锁
在这里插入图片描述

信号量(Semaphore)

trySetPermits()可以设置初始化信号量的个数

    /**
     * 车库停车
     * 3车位
     */
    @GetMapping("/part")
    @ResponseBody
    public String park() throws InterruptedException {
    
    
        //获取信号量
        RSemaphore semaphore = redissonClient.getSemaphore("park");
        //消耗线程数,默认1
        //占用车位1个
        //阻塞式获取,一定要获取到,获取不到我就阻塞
        semaphore.acquire();

        //尝试获取,能获取到就怎么,获取不到又怎么
        //不会死磕
//        boolean b = semaphore.tryAcquire();
//        if (b){
    
    
//            xxx
//        }else{
    
    
//            xxx
//        }

        return "ok";
    }
    //释放车位
    @GetMapping("/go")
    @ResponseBody
    public String go(){
    
    
        //获取信号量
        RSemaphore semaphore = redissonClient.getSemaphore("park");
        //释放一个车位,默认释放一个
        semaphore.release();
        return "ok";
    }

测试:
访问释放车位接口/go
在这里插入图片描述
释放一次,车位数就+1
在这里插入图片描述
访问/part停车接口,访问一次,车位数就减一

猜你喜欢

转载自blog.csdn.net/weixin_42412601/article/details/108248947
今日推荐