SpringBoot整合Redisson

Posted 后端小菜鸡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot整合Redisson相关的知识,希望对你有一定的参考价值。

Redisson官方文档: https://github.com/redisson/redisson/wiki

简介:Redisson 是架设在 Redis 基础上的一个 Java 驻内存数据网格(In-Memory Data Grid)。充分 的利用了 Redis 键值数据库提供的一系列优势,基于 Java 实用工具包中常用接口,为使用者 提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工 具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式 系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。 

一.简单使用

1、导入依赖

        <!--使用redisson作为分布式锁-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.16.8</version>
        </dependency>

2、新建Redisson配置

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;

@Configuration
public class MyRedissonConfig 
    /**
     * 所有对Redisson的使用都是通过RedissonClient对象
     * @return
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient()
        // 创建配置 指定redis地址及节点信息
        Config config = new Config();
        config.useSingleServer().setAddress("XXX.XX.XX.X(redis地址):端口").setPassword("xxxxxxxxx");

        // 根据config创建出RedissonClient实例
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    

 3、测试

 @Autowired
    RedissonClient redissonClient;


    @Test
    void redisson()
        System.out.println(redissonClient);
    

运行测试代码发现控制台有异常,报错信息为 :

java.lang.IllegalArgumentException: Redis url should start with redis:// or rediss:// (for SSL connection)

提示我们要在地址前加上redis:// ,SSL连接则需要加上rediss://

所以需要改为setAddress("redis://XXX.XX.XX.X:6379")即可

打印结果:

org.redisson.Redisson@6159fb3c

二.分布式锁

1、可重入锁

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

常用代码:

RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();

测试代码:

@ResponseBody
    @GetMapping("/hello")
    public String hello() 
        // 1.获取一把锁,只要锁的名字一样,就是同一把锁
        RLock lock = redisson.getLock("my-lock");
        lock.lock(); // 阻塞式等待。默认加的锁是30s时间
        try 
            // 1、锁的自动续期,运行期间自动给锁续上新的30s,无需担心业务时间长,锁过期会自动被释放
            // 2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动释放锁,锁默认在30s后自动释放,避免死锁
            System.out.println("加锁成功,执行业务代码..."+Thread.currentThread().getId());
            Thread.sleep(30000);
         catch (Exception e) 
            e.printStackTrace();
        finally 
            System.out.println("释放锁..."+Thread.currentThread().getId());
            lock.unlock();
        
        return "Hello!";
    

结果:

加锁成功,执行业务代码...99
释放锁...99

可以发现,当我们的业务超长时,运行期间,redisson会为我们自动续期锁,业务执行完将不会续期,即使不手动释放锁,锁也会默认在30s后释放。

2、看门狗机制

redisson 中提供的续期机制

开一个监听线程,如果方法还没执行完,就帮你重置 redis 锁的过期时间。

原理:

  1. 启动定时任务重新给锁设置过期时间,默认过期时间是 30 秒,每 10 秒(看门狗默认事件的1/3)续期一次(补到 30 秒)
  2. 如果线程挂掉(注意 debug 模式也会被它当成服务器宕机),则不会续期
  3. 只有lock.lock(); 会有看门狗机制;
  4. lock.lock(10,,TimeUnit.SECONDS);手动设置过期时间的话,则不会有看门狗机制(推荐)

3、读写锁

一次只有一个线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁.(该数据加写锁、读数据加读锁)

当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.
当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权, 但是如果线程希望以写模式对此锁进行加锁, 它必须直到所有的线程释放锁.

测试读写锁代码:

    
    @Autowired
    RedissonClient redisson;

    @Autowired
    RedisTemplate redisTemplate;

    @ResponseBody
    @GetMapping("/write")
    public String writeValue()
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        RLock rLock = lock.writeLock();
        String s = "";
        try 
            s = UUID.randomUUID().toString();
            // 模拟业务时间    
            Thread.sleep(30000);
         catch (Exception e)

        finally 
            rLock.unlock();
        
        redisTemplate.opsForValue().set("writeValue",s);
        return s;
    

    @GetMapping(value = "/read")
    @ResponseBody
    public String readValue() 
        String s = "";
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
        //加读锁
        RLock rLock = readWriteLock.readLock();
        try 
            rLock.lock();
            s = (String) redisTemplate.opsForValue().get("writeValue");

            TimeUnit.SECONDS.sleep(10);
         catch (Exception e) 
            e.printStackTrace();
         finally 
            rLock.unlock();
        
        return s;
    

当我们访问 localhost:10000/write写入数据时,因为线程睡眠了30s(模拟业务),此时我们访问  localhost:10000/read 将会一直阻塞,等待写锁释放,读锁才能占锁从而获取执行业务。

4、信号量

信号量为存储在redis中的一个数字,当这个数字大于0时,即可以调用acquire()方法增加数量,也可以调用release()方法减少数量,但是当调用release()之后小于0的话方法就会阻塞,直到数字大于0。 

可以应用于秒杀、限流等操作。

简单应用

    @GetMapping(value = "/park")
    @ResponseBody
    public String park() 
        RSemaphore park = redisson.getSemaphore("park");
        try 
            park.acquire();// 获取一个信号量(redis中信号量值-1),如果redis中信号量为0了,则在这里阻塞住,直到信号量大于0,可以拿到信号量,才会继续执行。
         catch (InterruptedException e) 
            e.printStackTrace();
        

        return "ok";
    

    @GetMapping(value = "/go")
    @ResponseBody
    public String go() 
        RSemaphore park = redisson.getSemaphore("park");
        park.release();  //释放一个信号量(redis中信号量值+1)
        return "ok";
    

其中:redisson.getSemaphore("park").acquire() 当信号量为0时将会一直阻塞,直到信号量大于0,才会继续执行。但redisson.getSemaphore("park").tryAcquire() 将不会阻塞,能拿到信号量就返回true,否则返回false,lock.tryLock() 同理。

5、闭锁

在要完成某些运算时,只有其它线程的运算全部运行完毕,当前运算才继续下去。

        模拟场景:

                学校放假,学校门卫锁门必须等待所有班级全部离开,才将学校大门锁住。

    @GetMapping(value = "/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException 
        RCountDownLatch lockDoor = redisson.getCountDownLatch("lockDoor");
        lockDoor.trySetCount(5); // 设置计数为5
        lockDoor.await(); //等待闭锁完成
        return "放假啦...";
    

    @GetMapping(value = "/go/id")
    public String go(@PathVariable("id") Integer id)  
        RCountDownLatch lockDoor = redisson.getCountDownLatch("lockDoor");
        lockDoor.countDown(); // 计数减1
        return id+"班都走光了";
    

结果:我们先访问 /lockDoor 线程将会阻塞,连续访问五次 /go/1 ,输出 放假了。

三.缓存数据一致性解决方案

 

尽量给锁加上过期时间;对于读写状态时,应该加上分布式读写锁。

redisson整合spring

原文:http://blog.csdn.net/wang_keng/article/details/73549274

 

首先讲下什么是Redisson:Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。(摘自redisson的wiki:wiki地址

redisson提供的api均以面向对象的操作方式,将key-value封装成我们熟悉的集合或者对象,我们可以通过封装的api更方便的操作数据。同时它提供了多个实现了java.util.corrurnent接口的集合类,让我们能在线程安全的环境下操作数据。

其中redis的官网也将它纳入推荐使用的工具中,可见redisson已经逐渐上位。

技术分享图片

 

废话不多说,现在开始讲下怎么整合redisson到spring:

首先引入redisson的包:(如果你还在用手动加jar包的方式,那建议你赶紧学一下maven,用上后会让你有种便秘一星期后突然通便的清爽)

[html] view plain copy
 
  1. <dependency>   
  2. <groupId>org.redisson</groupId>  
  3. <artifactId>redisson</artifactId>  
  4.   <version>2.8.2</version>   
  5. </dependency>  </span>  

接下来新建redisson.xml文件:(可参考wiki:redisson配置教程

 

[html] view plain copy
 
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.        xmlns:context="http://www.springframework.org/schema/context"  
  5.        xmlns:redisson="http://redisson.org/schema/redisson"  
  6.        xsi:schemaLocation="http://www.springframework.org/schema/beans  
  7.        http://www.springframework.org/schema/beans/spring-beans.xsd  
  8.        http://www.springframework.org/schema/context  
  9.        http://www.springframework.org/schema/context/spring-context.xsd  
  10.        http://redisson.org/schema/redisson  
  11.        http://redisson.org/schema/redisson/redisson.xsd">  
  12.    <!--  
  13.         单台redis机器配置    
  14.     <redisson:client id="redissonClient">  
  15.         <redisson:single-server address="192.168.2.100:7000" connection-pool-size="30"/>  
  16.     </redisson:client>  
  17.      -->  
  18.      <!-- redis集群配置 -->  
  19.      <redisson:client id="redissonClient" >  
  20.           
  21.         <redisson:cluster-servers scan-interval="10000">   <!-- //scan-interval:集群状态扫描间隔时间,单位是毫秒 -->  
  22.             <redisson:node-address value="192.168.2.100:7000"></redisson:node-address>  
  23.             <redisson:node-address value="192.168.2.100:7001"></redisson:node-address>  
  24.             <redisson:node-address value="192.168.2.100:7002"></redisson:node-address>  
  25.             <redisson:node-address value="192.168.2.100:7003"></redisson:node-address>  
  26.             <redisson:node-address value="192.168.2.100:7004"></redisson:node-address>  
  27.             <redisson:node-address value="192.168.2.100:7005"></redisson:node-address>  
  28.         </redisson:cluster-servers>  
  29.           
  30.     </redisson:client>  
  31. </beans> 


然后在Application.xml中引入redisson.xml:<import resource="classpath:/spring/redisson.xml" />     (路径记得自己改哦~)

至此我们redisson的环境就配置好了。接下来就是怎么操作它来操作redis:

首先新建一个工具类RedissonUtils:

 

[java] view plain copy
 

package com.basic.common.utils.redis;  

  1. import java.util.Map;  
  2. import org.redisson.api.RAtomicLong;  
  3. import org.redisson.api.RBucket;  
  4. import org.redisson.api.RCountDownLatch;  
  5. import org.redisson.api.RDeque;  
  6. import org.redisson.api.RList;  
  7. import org.redisson.api.RLock;  
  8. import org.redisson.api.RMap;  
  9. import org.redisson.api.RQueue;  
  10. import org.redisson.api.RSet;  
  11. import org.redisson.api.RSortedSet;  
  12. import org.redisson.api.RTopic;  
  13. import org.redisson.api.RedissonClient;  
  14. import org.springframework.stereotype.Service;  
  15. import com.basic.common.utils.generator.CollectionObjectConvert;  
  16. @Service  
  17. public class RedissonUtils{  
  18. /**  
  19.      * 获取字符串对象  
  20.      * @param redisson  
  21.      * @param t  
  22.      * @param objectName  
  23.      * @return  
  24.      */    
  25. public static <T> RBucket<T> getRBucket(RedissonClient redissonClient,String objectName){    
  26.         RBucket<T> bucket=redissonClient.getBucket(objectName);    
  27. return bucket;    
  28.     }    
  29. /**  
  30.      * 获取Map对象  
  31.      * @param redisson  
  32.      * @param objectName  
  33.      * @return  
  34.      */    
  35. public static <K,V> RMap<K, V> getRMap(RedissonClient redissonClient,String objectName){    
  36.         RMap<K, V> map=redissonClient.getMap(objectName);    
  37. return map;    
  38.     }    
  39. /**  
  40.      * 获取有序集合  
  41.      * @param redisson  
  42.      * @param objectName  
  43.      * @return  
  44.      */    
  45. public static <V> RSortedSet<V> getRSortedSet(RedissonClient redissonClient,String objectName){    
  46.         RSortedSet<V> sortedSet=redissonClient.getSortedSet(objectName);    
  47. return sortedSet;    
  48.     }    
  49. /**  
  50.      * 获取集合  
  51.      * @param redisson  
  52.      * @param objectName  
  53.      * @return  
  54.      */    
  55. public static <V> RSet<V> getRSet(RedissonClient redissonClient,String objectName){    
  56.         RSet<V> rSet=redissonClient.getSet(objectName);    
  57. return rSet;    
  58.     }    
  59. /**  
  60.      * 获取列表  
  61.      * @param redisson  
  62.      * @param objectName  
  63.      * @return  
  64.      */    
  65. public static <V> RList<V> getRList(RedissonClient redissonClient,String objectName){    
  66.         RList<V> rList=redissonClient.getList(objectName);    
  67. return rList;    
  68.     }    
  69. /**  
  70.      * 获取队列  
  71.      * @param redisson  
  72.      * @param objectName  
  73.      * @return  
  74.      */    
  75. public static <V> RQueue<V> getRQueue(RedissonClient redissonClient,String objectName){    
  76.         RQueue<V> rQueue=redissonClient.getQueue(objectName);    
  77. return rQueue;    
  78.     }    
  79. /**  
  80.      * 获取双端队列  
  81.      * @param redisson  
  82.      * @param objectName  
  83.      * @return  
  84.      */    
  85. public static <V> RDeque<V> getRDeque(RedissonClient redissonClient,String objectName){    
  86.         RDeque<V> rDeque=redissonClient.getDeque(objectName);    
  87. return rDeque;    
  88.     }    
  89. /**  
  90.      * 此方法不可用在Redisson 1.2 中   
  91.      * 在1.2.2版本中 可用  
  92.      * @param redisson  
  93.      * @param objectName  
  94.      * @return  
  95.      */    
  96. /**  
  97.     public <V> RBlockingQueue<V> getRBlockingQueue(Redisson redisson,String objectName){  
  98.         RBlockingQueue rb=redisson.getBlockingQueue(objectName);  
  99.         return rb;  
  100.     }*/    
  101. /**  
  102.      * 获取锁  
  103.      * @param redisson  
  104.      * @param objectName  
  105.      * @return  
  106.      */    
  107. public static RLock getRLock(RedissonClient redissonClient,String objectName){    
  108.         RLock rLock=redissonClient.getLock(objectName);    
  109. return rLock;    
  110.     }    
  111. /**  
  112.      * 获取原子数  
  113.      * @param redisson  
  114.      * @param objectName  
  115.      * @return  
  116.      */    
  117. public static RAtomicLong getRAtomicLong(RedissonClient redissonClient,String objectName){    
  118.         RAtomicLong rAtomicLong=redissonClient.getAtomicLong(objectName);    
  119. return rAtomicLong;    
  120.     }    
  121. /**  
  122.      * 获取记数锁  
  123.      * @param redisson  
  124.      * @param objectName  
  125.      * @return  
  126.      */    
  127. public static RCountDownLatch getRCountDownLatch(RedissonClient redissonClient,String objectName){    
  128.         RCountDownLatch rCountDownLatch=redissonClient.getCountDownLatch(objectName);    
  129. return rCountDownLatch;    
  130.     }    
  131. /**  
  132.      * 获取消息的Topic  
  133.      * @param redisson  
  134.      * @param objectName  
  135.      * @return  
  136.      */    
  137. public static <M> RTopic<M> getRTopic(RedissonClient redissonClient,String objectName){    
  138.          RTopic<M> rTopic=redissonClient.getTopic(objectName);    
  139. return rTopic;    
  140.     }    
  141. }  
  142.  


然后我们需要在调用RedissonUtils的类中依赖进RedissonClient 

@Autowired  

private RedissonClient redisson;

只后便可以对redis进行操作啦

RMap<String, Object> map = RedissonUtils.getRMap(redisson,key);

map.put("key","value");     调用到这个语句的同时就已经对redis中的数据进行了修改。 

 

注意!redis最好是3.0以上的,官方发布的版本,不然会出现莫名其妙的错误

 





以上是关于SpringBoot整合Redisson的主要内容,如果未能解决你的问题,请参考以下文章

[SpringBoot系列]SpringBoot如何整合SSMP

springboot怎么整合activiti

SpringBoot完成SSM整合之SpringBoot整合junit

springboot整合jedis

SpringBoot 整合其他框架 -- SpringBoot整合Mybatis

SpringBoot 整合其他框架 -- SpringBoot整合Junit