springboot整合redisson做分布式锁


一、简介

redisson操作redis的一款框架,用来处理分布式锁等功能的。支持Java语言

redisson的github地址上面有详细的方法介绍==>官网地址

注明:如果是其他语言的分布式锁,可以去看redis官网 ==> 官网地址
在这里插入图片描述

分布式情况下采用Redisson做分布式锁


如果是单体应用(就一个服务):我们完成可以采用本地锁(synchronized、lock)在进行同步。要区分好场景


本地锁 synchronized (this){} 同步代码块。只要是同一把锁,就能锁住需要这个锁的多线程
this==>springboot所有的组件在容器中都是单例的。所有注意这个this是唯一的


二、引入redisson的依赖

<!--引入redisson,作为所有分布式锁,分布式对象等功能的框架-->
<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson</artifactId>
     <version>3.12.0</version>
</dependency>

下面的案例会用到操作redis的所有也将redis依赖引入

<!-
	这边是排除了lettuce,使用jedis做底层客户端
	但是还是推荐是lettuce做底层客户端,他使用netty通信,吞吐量更大(不排除lettuce,就不用引入jedis)
	lettuce有个bug。在压力测试的时候会产生堆外内存溢出,OutOfDirectMemoryError
		但是据说已经修复了
	jedis是稳定,但是太久没更新了
-->
<!--spring-boot-starter-data-redis-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
     <!--  排除掉lettuce客户端   -->
     <exclusions>
         <exclusion>
             <groupId>io.lettuce</groupId>
             <artifactId>lettuce-core</artifactId>
         </exclusion>
     </exclusions>
 </dependency>

 <!--引入jedis-->
 <dependency>
     <groupId>redis.clients</groupId>
     <artifactId>jedis</artifactId>
 </dependency>

三、配置redisson

小编采用程序化配置、单节点模式

3.1、编写配置文件

MyRedissonConfig

package sqy.redissondome.config.redis;

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;

import java.io.IOException;

/**
 * Redisson配置类
 * @author suqinyi
 * @Date 2022/1/14
 */
@Configuration
public class MyRedissonConfig {

    /**
     * 官网配置说明:
     *      https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95
     *
     * Redisson来操作redis
     * 所有对Redisson的使用都是通过RedissonClient对象
     * 配置方法有
     *  第一种、 程序化配置方法
     *         config.useSingleServer().setAddress("redis://127.0.0.1:6379");
     *  第二种、 文件方式配置
     *         Config config = Config.fromYAML(new File("config-file.yaml"));
     *
     *  这是集群的程序化配置方法(方法1)
     * .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException {
        //创建配置
        Config config = new Config();
        //redis的连接地址,这个是小编虚拟机的redis
        //这边采用了【单节点模式】 - redis地址
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        //根据config创建出RedissonClient的实列
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }


//      //法一
//    @Bean
//    public RedissonClient getRedisson() {
//        Config config = new Config();
//        if(colony) {
//            config.useClusterServers()
//                    .setScanInterval(1000 * 2)
//                    .addNodeAddress(addressUrl)
//                    .setPassword(password)
//                    .setReconnectionTimeout(10000)
//                    .setRetryInterval(5000)
//                    .setTimeout(10000)
//                    .setConnectTimeout(10000);
//
//        }else {
//            config.useSingleServer()
//                    .setAddress(addressUrl).setPassword(password)
//                    .setReconnectionTimeout(10000)
//                    .setRetryInterval(5000)
//                    .setTimeout(10000)
//                    .setConnectTimeout(10000);
//        }
//
//        return Redisson.create(config);

//        //法二
//        config.useMasterSlaveServers()
//                //可以用"rediss://"来启用SSL连接
//                .setMasterAddress(addressUrl).setPassword(password)
//                .addSlaveAddress(addressUrlSlave)
//                .setReconnectionTimeout(10000)
//                .setRetryInterval(5000)
//                .setTimeout(10000)
//                .setConnectTimeout(10000);//(连接超时,单位:毫秒 默认值:3000);

        //  哨兵模式config.useSentinelServers().setMasterName("mymaster").setPassword("web2017").addSentinelAddress("***(哨兵IP):26379", "***(哨兵IP):26379", "***(哨兵IP):26380");

}

在配置下redis

spring:
  #配置redis
  redis:
    host: 192.168.56.10
    port: 6379

3.2、官网的说明

3.2.1、配置方法==>入口
在这里插入图片描述
3.2.2、共有俩种配置方法

  1. 程序化配置方法
  2. 文件方式配置

3.2.3、单节点模式
因为小编没有把redis搭集群,所有采用的是单节点模式

独立节点 ==> 入口
在这里插入图片描述

代码如下(示例):

四、参照分布式锁和同步器

4.1、官网

地址==>入口
在这里插入图片描述

4.2、简单概述

  1. 分布式锁里面介绍了很多:可重入锁、公平锁、联锁、读写锁、 红锁
  2. 我们经常使用的是:可重入锁和读写锁
  3. 信号量(Semaphore):我们可以用来做限流
  4. 还有一个经常使用的:闭锁(CountDownLatch)也可以成为等待锁

可重入锁

基于Redis的Redisson分布式可重入锁RLock
Java对象实现了java.util.concurrent.locks.Lock接口。
同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

所以 ==> 以前Lock是怎么使用的、JUC是怎么使用的,redisson就怎么使用

五、案例演示

5.0、统一说明

记得注入RedissonClient 和StringRedisTemplate

 	@Autowired
    RedissonClient redisson;
    @Autowired
    StringRedisTemplate redisTemplate;

依赖包不要导入错了

import org.springframework.data.redis.core.StringRedisTemplate;
import org.redisson.api.*;

5.1、可重入锁(Reentrant Lock)

注:说明和源码的核心,小编写在注释里面了,可以自己搜索类进行看源码

  1. 看门狗机制,自动续期
  2. 自己传入过期时间,将不自动续期
  3. 推荐采用自己传入过期方式的模式,省掉了整个续期操作
 	@ResponseBody
    @GetMapping("/hello")
    public String hello() {
        // 获取一把锁,只要锁的名字一样,就是同一把锁
        RLock lock = redisson.getLock("my-lock");

        //方式一、加锁,默认的
        lock.lock();//阻塞式等待。默认加的锁都是30秒时间
        //方式二、可以指定时间,锁到期以后没有自动续期 ==> 所以自动解锁的时间一定要保证大于业务的执行时间
        //lock.lock(10,TimeUnit.SECONDS);//10s自动解锁

        //源代码:RedissonLock ==> 利用 while(ture)
      
        /**
         * 看门狗机制
         * 1、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s。不用担心因为业务时间长,锁自动过期被删除
         * 2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除
         *
         * 问题: lock.lock(10,TimeUnit.SECONDS) 在锁过期不会自动续期
         *      1、如果我们传递了锁的过期时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
         *      2、如果没有指定锁的超时时间,就使用30000L(LockWatchdogTimeout 看门狗的默认时间)
         *          只要占锁成功就会启动一个定时任务【就会重新给锁设置过期时间,新的时间就是看门狗的默认时间】,每隔10s都会自动续期,续期成30s
         *          this.internalLockLeaseTime(看门狗时间) / 3L   也就是10s续期一次
         */

        /**
         * 实战的使用推荐
         *    推荐使用:lock.lock(10,TimeUnit.SECONDS) 省掉了整个续期操作。手动解锁
         */
        try {
          	//业务
            System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
            //模拟业务耗时
            Thread.sleep(30000);
        } catch (Exception e) {

        } finally {
            /**
             * 假设出现闪断,解锁代码没有运行,redisson会不会出现死锁?
             * 不会出现死锁,锁的
             */
            //解锁
            System.out.println("释放锁..." + Thread.currentThread().getId());
            lock.unlock();
        }
        return "hello";
    }

测试:同时发送2个hello请求

效果:会等一个操作完,第二个才能执行
在这里插入图片描述

5.2、读写锁(ReadWriteLock)

说明:准备2个方法,一个读取数据,一个写入数据

读写锁的好处小编之间总结在代码注释上面

	/**
     * 读写锁的好处:
     * 修改数据期间,写锁是一个排他锁(互斥锁\独享锁),读锁是一个共享锁
     * 写锁没释放,读锁就必须等着
     * 这样能保证读到最新数据
     * 读 + 读  相当于无锁(并发读,只会在redis中记录好,所有当前的读锁,他们都会同时加锁成功)
     * 写 + 读  等待写锁释放
     * 写 + 写  阻塞方式
     * 读 + 写  有读锁,写也需要等待
     * <p>
     * 只要有写的存在,都必须等待
     */

    @ResponseBody
    @GetMapping("/write")
    public String writeValue() {
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        /**
         * 改数据加写锁,读数据加读锁
         */
        //加 写锁
        RLock rLock = lock.writeLock();
        rLock.lock();
        String s = "";
        try {
            System.out.println("写锁加锁成功。。。" + Thread.currentThread().getId());
            s = UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("writeValue", s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("写锁释放成功。。。" + Thread.currentThread().getId());
            //解锁
            rLock.unlock();
        }
        return s;
    }

    @ResponseBody
    @GetMapping("/read")
    public String readValue() {
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        //加 读锁
        RLock rLock = lock.readLock();
        rLock.lock();
        String s = "";
        try {
            System.out.println("读锁加锁成功。。。" + Thread.currentThread().getId());
            s = redisTemplate.opsForValue().get("writeValue");
            /**
             * 这边测试,先读在写
             * 让读也延时30秒,看写锁是否需要等待
             */
            Thread.sleep(30000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("读锁释放成功。。。" + Thread.currentThread().getId());
            rLock.unlock();
        }
        return s;
    }

测试:我们可以模拟并发读写

  1. 测试1:先发一个写,在发2个读
  2. 测试2:先发一个读,在发2个写
  3. 测试3:发送3个读
  4. 测试4:发送2个写
  5. 这边小编就不把一个一个效果截图了。截图个,先1写后2读
  6. 说明:你们可以将配置复制一份,修改名称和端口,开2个服务。这样来模拟2个服务器,同时,进行读写操作。效果是一样的
    在这里插入图片描述

5.3、信号量(Semaphore)

准备3个方法:一个在redis中存入数据,一个获取数据,一个释放数据

	/**
     * 信号量(Semaphore)
     * 都是阻塞方法
     * acquire() :会一直等,等到redis有数据,他就马上执行
     * tryAcquire():进行判断,如果为false,直接返回
     * 可以用于限流
     * 车库停车 ==> 一共3位置
     */

    //存入测试数据
    @GetMapping("/setPark")
    @ResponseBody
    public String setPark() {
        redisTemplate.opsForValue().set("park", "3");
        return "一共有3个空闲";
    }

    //获取一个数据(acquire)
    @GetMapping("/park")
    @ResponseBody
    public String park() throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
        park.acquire();//获取一个信息(获取一个值)占一个车位
        //执行业务
        String s = redisTemplate.opsForValue().get("park");
        return "redis剩余:" + s;

    }

    //获取一个数据(trypark)
    @GetMapping("/trypark")
    @ResponseBody
    public String trypark() throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
        boolean b = park.tryAcquire();
        if (b) {
            //执行业务
            String s = redisTemplate.opsForValue().get("park");
            return "redis剩余:" + s + "  " + b;
        } else {
            return "使用tryAcquire限流。封顶了你被直接返回";
        }
    }

    //释放一个数据
    @GetMapping("/go")
    @ResponseBody
    public String go() throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
        park.release();//释放一个信息(释放一个值)释放一个车位
        String s = redisTemplate.opsForValue().get("park");
        return "redis剩余:" + s;
    }

测试:

  1. 首先运行setPark方法
  2. 在运行park方法(或者trypark方法),运行3次以上,将存入redis的数据减少
  3. 在运行go方法,给redis里面存值
  4. 这种我们可以用来做限流操作
  5. 效果图小编就不贴了

5.4、闭锁(CountDownLatch

	/**
     * 放假,锁门
     * 5个班级全部走完,锁大门
     */

    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.trySetCount(5);//设置5个班
        door.await();//等待闭锁都完成
        return "放假了..";
    }

    //这个接口必须请求5次,闭锁才能释放
    @GetMapping("/gogogo/{id}")
    @ResponseBody
    public String gogogo(@PathVariable("id") Long id){
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown();//计数减一。
        return id+"班的人走完了。。。";
    }

测试:

  1. 首先运行lockDoor方法。他就会一直转圈圈等待
  2. 在运行/gogogo/{id}方法。需要运行5次。这个闭锁才会释放

五、缓存里面的数据如何数据库保持一致

常见的2中模式:

  1. 双写模式:修改数据库数据的时候,同时修改缓存的数据
  2. 失效模式:修改数据库数据的时候,将缓存的数据直接删除,等待下次主动进行查询进行更新,存入缓存
  3. 这俩种模式:都可能因为网络问题,或者其他问题出现,在并发读写的情况下,产生脏数据(缓存和数据库最新数据不一致)
  4. 解决这个问题可以采用阿里的开源中间件Canal订阅来解决。但是更多的中间件加入会损耗一定的性能,对于频繁读写的数据,保持缓存数据一致性,我们都可以通过加锁来实现,但是要考虑,有没有必要,很多锁,也会导致系统(服务)变得缓慢
 系统的一致性解决方案:
     1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
     2、读写数据的时候,加上分布式的读写锁。
     经常写,经常读
 
     有没有必要存入缓存,加锁也会影响效率
     对经常读写的数据,要求实时性高的,直接去查数据库。

其他缓存当然还有:spring cache、guava…

推荐来看看官网的第三方框架整合(整合spring cache) ==> 入口
讲到分布式锁,就要有分布式事务。这边只能分篇章进行小结


完结

其他的比如说:集群、分片…之间参考官方文档搞就行了

面向官方文档编程

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以为您提供有关于SpringBoot整合Redisson实现分布式锁的相关信息。 首先,您需要在您的SpringBoot项目中添加Redisson的依赖,可以在pom.xml文件中添加如下依赖: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>${redisson.version}</version> </dependency> ``` 其中,`${redisson.version}`为Redisson的版本号。 接下来,在您的项目中创建一个RedissonConfig类,用于配置Redisson连接池和RedissonClient对象的实例化。示例代码如下: ```java @Configuration public class RedissonConfig { @Autowired private RedisProperties redisProperties; @Bean public RedissonClient redissonClient() { Config config = new Config(); String address = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort(); config.useSingleServer().setAddress(address).setDatabase(redisProperties.getDatabase()) .setPassword(redisProperties.getPassword()); return Redisson.create(config); } } ``` 在上述示例代码中,我们通过读取Redis的连接配置信息,使用单节点连接Redis。您也可以根据您的实际需要进行配置。 接下来,我们可以通过RedissonClient对象来获取分布式锁。示例代码如下: ```java @Autowired private RedissonClient redissonClient; public void acquireLock() { RLock lock = redissonClient.getLock("myLock"); lock.lock(); try { // 这里是您的业务逻辑代码 } finally { lock.unlock(); } } ``` 在上述示例代码中,我们首先通过`redissonClient.getLock("myLock")`获取一个名为"myLock"的分布式锁。然后,我们调用`lock.lock()`方法来获取锁,如果获取失败则会一直阻塞直到获取成功。在业务逻辑执行完成后,我们通过`lock.unlock()`方法来释放锁。 以上就是使用Redisson实现分布式锁整合方式。希望对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

suqinyi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值