Redisson分布式锁

前言

在redis分布式锁中,我们一步步的分析,实现了原生的redis分布式锁,但是这样操作实在是麻烦,因此,这里介绍下一个开源的redis分布式锁框架——redisson,看下它是怎么使用的。

一、引入&配置

1、项目中引入redisson的依赖


            org.redisson
            redisson
            3.12.0
        

2、配置redisson

可以配置单机模式、集群模式等等,这里以单机模式为例

package com.example.redis.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author zy
 * @version 1.0.0
 * @ClassName RedissonConfig.java
 * @Description 操作redisson的redissonClient对象
 * @createTime 2023/4/10
 */
@Configuration
public class RedissonConfig {
    @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://ip:port").setPassword("没有密码可以忽略");
        //创建redisson实例
        RedissonClient redissonClient = Redisson.create(config);
        /**
         * 集群模式
        Config config = new Config();
        config.useClusterServers()
                .addNodeAddress("redis://ip1:端口1")
                .addNodeAddress("redis://ip1:端口2")
                .addNodeAddress("redis://ip1:端口3")
                .addNodeAddress("redis://ip2:端口1")
                .addNodeAddress("redis://ip2:端口2")
                .addNodeAddress("redis://ip2:端口3");
        RedissonClient redissonClient = Redisson.create(config);
         **/
        return redissonClient;
    }
}

二、使用redisson实现几种锁

1、可重入锁

/**
     * 首先,redisson的使用非常简单,我们配置好redisson客户端,然后关注加锁解锁即可
     * RLock lock = redisson.getLock("lock name");
     * lock.lock();
     * lock.unlock();
     * 几个声明点:
     * 1、redisson所有指令都通过lua脚本执行(我们在原生的redis分布式锁中也用到lua脚本加解锁)
     * 2、redisson设置一个key的默认过期时间为30s(那么如果业务执行时间超过30s怎么办?能否传入自定义时间?)
     *  2.1、redisson有一个锁自动续期机制(看门狗机制),只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s续成满时间30s
     *      这里要注意的是使用默认锁时间
     *  2.2、当然可以自定义时间,lock.lock(10, TimeUnit.SECONDS);但是需要注意自定义时间,没有看门狗机制
     * 3、看门狗机制会有死锁吗?如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了
     */

    /**
     * 可重入锁实现
     * @return
     */
    @RequestMapping("/reenterLock")
    public String reenterLock(){
        //调用getLock获取一把锁,相同的锁名字就是同一把锁
        RLock lock = redissonClient.getLock("my-reenterLock");
        //加锁
        lock.lock();
        try {
            System.out.println("加锁成功,开始执行业务,线程ID为:"+Thread.currentThread().getId());
            Thread.sleep(35*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //解锁
            lock.unlock();
            System.out.println("业务执行完毕,释放锁成功,线程ID为:"+Thread.currentThread().getId());
        }
        return "hello";
    }

2、读写锁

/**
     * 实现读写锁:读写锁保证一定能读取到最新数据,写数据期间是一个独占锁(互斥、排他)
     * 核心思想:读锁是一个共享锁,写锁是一个独占锁,写锁没释放,读锁就一直等待
     * 几种情况:
     *      读+读:相当于无锁,可以并发读,都会加锁成功
     *      写+读:等待写锁释放
     *      写+写:阻塞
     *      读+写:需要等待读锁释放才能写
     * 只要有写锁,就要等待
     * 通过接口多次请求及不同的组合方式测试上述情况
     * @return
     */
    @RequestMapping("/write")
    public String write(){
        System.out.println("写锁业务.....");
        //声明同一把锁
        RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
        //读写变量
        String s = "";
        //声明写锁
        RLock writeLock = lock.writeLock();
        try {
            //写数据加写锁
            writeLock.lock();
            System.out.println("写锁加锁成功,线程ID为:"+Thread.currentThread().getId());
            //修改变量,模拟业务
            s= UUID.randomUUID().toString();
            Thread.sleep(35*1000);
            //放入缓存,为了能读写同一个地方
            redisTemplate.opsForValue().set("read-write-value",s);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            writeLock.unlock();
            System.out.println("写锁释放成功,线程ID为:"+Thread.currentThread().getId());
        }
        return s;
    }

    @RequestMapping("read")
    public String read(){
        System.out.println("读锁业务......");
        //声明同一把锁
        RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
        String s = "";
        //声明读锁
        RLock readLock = lock.readLock();
        try {
            //读数据加读锁
            readLock.lock();
            System.out.println("读锁加锁成功,线程ID为:"+Thread.currentThread().getId());
            //模拟业务
            Thread.sleep(35*1000);
            s = String.valueOf(redisTemplate.opsForValue().get("read-write-value"));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readLock.unlock();
            System.out.println("读锁解锁成功,线程ID为:"+Thread.currentThread().getId());
        }
        return s;
    }

3、信号量

/**
     * 实现信号量:分布式信号量,用来做限流
     * 场景:停车问题,一共3个车位,每次来车判断是否有车位,有就停,没有就开走
     * 分为停车和取车两个方法
     * @return
     */
    @RequestMapping("/initPark")
    public void initPark(){
        //初始化车位
        redisTemplate.opsForValue().set("park",3);
    }
    @RequestMapping("/park")
    public String park() throws InterruptedException {
        RSemaphore park = redissonClient.getSemaphore("park");
        //获取一个信号量,获取不到会一直等待
        //park.acquire();
        boolean empty = park.tryAcquire();//尝试获取车位,没有就离开
        return "可以停车吗=>"+empty;
    }
    @RequestMapping("/move")
    public String move(){
        RSemaphore park = redissonClient.getSemaphore("park");
        park.release();//信号量释放,即释放车位
        return "ok";
    }

4、闭锁

/**
     * 实现CountDownLatch(闭锁)
     * 场景:一个班有五个人,等人走完了才锁门
     * 调用锁门方法,如果一直有人,会等待,直到所有人走完,才执行完毕
     */
    @RequestMapping("/lockDoor")
    public String lockDoor() throws InterruptedException {
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.trySetCount(5);
        door.await();//等待所有人走完
        return "人都走了,锁门......";
    }
    @RequestMapping("/go")
    public String go(){
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        door.countDown();
        return "还剩"+door.getCount()+"个人没走";
    }

5、联锁

@RequestMapping("/mutiLock")
    public String mutiLock(){
        RLock lock1 = redissonClient.getLock("lock1");
        RLock lock2 = redissonClient.getLock("lock2");
        RLock lock3 = redissonClient.getLock("lock3");
        RLock lock = redissonClient.getMultiLock(lock1,lock2,lock3);
        boolean flag = lock.tryLock();
        if(flag){
            try{
                System.out.println("联锁加锁成功");
                //业务
            }catch (Exception e){

            }finally {
                //释放锁
                lock.unlock();
            }
        }
        return "ok";
    }

总结

这篇文章只要介绍redisson的使用,实现了可重入锁、信号量、读写锁等常见的锁,redisson的强大不只这些,还有公平锁等实现,这篇文章主要贴近应用,如果需要看原理,还需深入学习,或者看下前文从Reentrantlock看AQS独占式锁原理、Condition接口在AQS中实现的原理分析、Semaphore浅析、ReentrantLock的源码分析,对理解源码有些帮助。

你可能感兴趣的:(学习总结,分布式,java,redis)