Redisson 作为一款 Redis Java 客户端,为我们提供了非常多便捷的功能。在绝大多数场景下,使用 Redisson 可以让开发人员将关注点放在业务上,而并不用关心 Redis 的处理过程。并且在分布式系统中,Redisson 得到了广泛的应用。

本文将简单介绍 SpringBoot 如何集成使用 Redisson

🚀 Redisson Wiki 文档:https://github.com/redisson/redisson/wiki

🚀 源码地址:https://github.com/NekoChips/SpringDemo/18.springboot-redisson


1. pom 依赖

        <!-- redission -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.12.3</version>
        </dependency>

2. 配置 Redisson

官网上的介绍,Redisson 的配置方式有很多种。这里做一下引用

程序化配置方式

Config config = new Config();
config.setTransportMode(TransportMode.EPOLL);
config.useClusterServers()
      //可以用"rediss://"来启用SSL连接
      .addNodeAddress("redis://127.0.0.1:7181");

Json 文件配置方式

Config config = Config.fromJSON(new File("config-file.json"));
RedissonClient redisson = Redisson.create(config);

Yaml 文件配置方式

这种方式是个人比较喜欢的方式,这里使用的是 fromYAML(String url)

Config config = Config.fromYAML(RedissonConfig.class.getClassLoader().getResource("redisson.yaml"));
RedissonClient redission = Redisson.create(config);

3. YAML 配置文件

## Redisson 配置
singleServerConfig:
  address: redis://127.0.0.1:6379
  password: 123456
  ## 客户端名称
  clientName: Redisson-client
  ## 使用数据库索引
  database: 7
  ## 最大空闲时间
  idleConnectionTimeout: 10000
  ## 连接超时时间
  connectTimeout: 10000
  ## 返回超时时间
  timeout: 2000
  ## 重试次数
  retryAttempts: 3
  ## 重试间隔时间
  retryInterval: 1500
  ## 最小订阅连接数
  subscriptionConnectionMinimumIdleSize: 1
  ## 订阅连接池大小
  subscriptionConnectionPoolSize: 50
  ## 最小连接数
  connectionMinimumIdleSize: 10
  ## 连接池大小
  connectionPoolSize: 64

## 线程池数量,默认为 当前处理核数量 * 2
threads: 0
## netty 线程池数量,默认为 当前处理核数量 * 2
nettyThreads: 1
## 序列化编码方式
codec:
  class: org.redisson.codec.JsonJacksonCodec
## 传输模式,默认为 TransportMode.NIO,可选参数为TransportMode.EPOLL、TransportMode.KQUEUE
transportMode: NIO
## 监控锁的看门狗超时,只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况,默认值30000
lockWatchdogTimeout: 30000

其实 Redisson 默认已经配置了很多东西,具体可以 Redisson 配置类源码。

可以看到针对不同的模式我们可以作相应的配置,如 Redis 哨兵Redis 主从同步单机 Redis 模式Redis 集群 等等。

这里我们只做了单机模式的配置。

public class Config {

    private SentinelServersConfig sentinelServersConfig;

    private MasterSlaveServersConfig masterSlaveServersConfig;

    private SingleServerConfig singleServerConfig;

    private ClusterServersConfig clusterServersConfig;

    private ReplicatedServersConfig replicatedServersConfig;

    private ConnectionManager connectionManager;

    /**
     * Threads amount shared between all redis node clients
     */
    private int threads = 16;

    private int nettyThreads = 32;

    /**
     * Redis key/value codec. FST codec is used by default
     */
    private Codec codec;

    private ExecutorService executor;

    /**
     * Config option for enabling Redisson Reference feature.
     * Default value is TRUE
     */
    private boolean referenceEnabled = true;

    private TransportMode transportMode = TransportMode.NIO;

    private EventLoopGroup eventLoopGroup;

    private long lockWatchdogTimeout = 30 * 1000;

    private boolean keepPubSubOrder = true;

    private boolean decodeInExecutor = false;

    private boolean useScriptCache = false;

    private int minCleanUpDelay = 5;

    private int maxCleanUpDelay = 30*60;

    private int cleanUpKeysAmount = 100;

    //  ......

}

4. 构建 Redisson 客户端

编写一个 config 类,注册 RedissonClientSpring 中即可。

@Configuration
public class RedissonConfig {

    private Logger logger = LoggerFactory.getLogger(RedissonConfig.class);

    @Bean
    public RedissonClient redissonClient() {
        try {
            Config config = Config.fromYAML(RedissonConfig.class.getClassLoader().getResource("redisson.yaml"));
            RedissonClient redission = Redisson.create(config);
            return redission;
        } catch (IOException e) {
            logger.error("file not found which name is redisson.yaml");
        }
        return null;
    }
}

5. 测试 Redisson 功能

可以说 Redisson 的功能非常强大,并且开箱即用非常方便。下面我们一起来看一下使用的较多的功能。

RBucket

对应 RedisString 数据格式,通过配置中的序列化方式对对象进行序列化处理。

    @Test
    public void testSaveAndGet() {
        Student student = new Student("001", "Jimmy", "F");

        RBucket<Student> rBucket = redissonClient.getBucket("cache:student:" + student.getStudentId());
        rBucket.set(student, 30000L, TimeUnit.MILLISECONDS);

        System.out.println(rBucket.get());
        redissonClient.shutdown();
    }

Redis 查询结果

aliyun-redis:7>select 7
"OK"

aliyun-redis:7>get cache:student:001
"{"@class":"com.demo.redisson.bean.Student","name":"Jimmy","sex":"F","studentId":"001"}"

控制台输出

Student{studentId='001', name='Jimmy', sex='F'}

RList

对应 List 数据结构

    @Test
    public void testRList() {
        List<Student> students = new ArrayList<>();
        students.add(new Student("001", "Jimmy", "F"));
        students.add(new Student("002", "Jack", "M"));
        students.add(new Student("003", "Julia", "F"));

        RList<Student> rList = redissonClient.getList("cache:list");
        rList.clear();

        // addAll
        rList.addAll(students);
        // add
        rList.add(new Student("004", "Jane", "F"));
        // addAfter
        rList.addAfter(new Student("001", "Jimmy", "F"), new Student("005", "Neko", "M"));
        // get(int index)
        System.out.println("index 1 : " + rList.get(1));

        // 获取下标为 0 和 3 的两个元素组成的列表
        // get(int...indexes)
        rList.get(0, 3).forEach(System.out::println);
        redissonClient.shutdown();
    }

Redis 查询结果

aliyun-redis:7>lrange cache:list 0 -1
 1)  "{"@class":"com.demo.redisson.bean.Student","name":"Jimmy","sex":"F","studentId":"001"}"
 2)  "{"@class":"com.demo.redisson.bean.Student","name":"Neko","sex":"M","studentId":"005"}"
 3)  "{"@class":"com.demo.redisson.bean.Student","name":"Jack","sex":"M","studentId":"002"}"
 4)  "{"@class":"com.demo.redisson.bean.Student","name":"Julia","sex":"F","studentId":"003"}"
 5)  "{"@class":"com.demo.redisson.bean.Student","name":"Jane","sex":"F","studentId":"004"}"

控制台输出

index 1 : Student{studentId='005', name='Neko', sex='M'}
Student{studentId='001', name='Jimmy', sex='F'}
Student{studentId='003', name='Julia', sex='F'}

RMap

对应 Map 数据结构

    @Test
    public void testRMap() {
        RMap<String, Student> rMap = redissonClient.getMap("cache:map");
        rMap.clear();

        rMap.put("001", new Student("001", "Jimmy", "F"));
        rMap.put("002", new Student("002", "Jack", "M"));
        rMap.put("003", new Student("003", "Julia", "F"));
        rMap.put("004", new Student("004", "Jane", "F"));

        // containsKey
        logger.info("rMap contains key '005' : {}", rMap.containsKey("005"));
        // size
        logger.info("rMap size : {}", rMap.size());
        // get(Object key)
        logger.info("rMap get value by '002' : {}", rMap.get("002"));
        // putIfAbsent
        logger.info("rMap put value in existed key '001' : {}", rMap.putIfAbsent("001", new Student()));
        redissonClient.shutdown();
    }

Redis 查询结果

aliyun-redis:7>HSCAN cache:map 0 COUNT 10
 1)   ""003""
 2)   "{"@class":"com.demo.redisson.bean.Student","name":"Julia","sex":"F","studentId":"003"}"
 3)   ""002""
 4)   "{"@class":"com.demo.redisson.bean.Student","name":"Jack","sex":"M","studentId":"002"}"
 5)   ""004""
 6)   "{"@class":"com.demo.redisson.bean.Student","name":"Jane","sex":"F","studentId":"004"}"
 7)   ""001""
 8)   "{"@class":"com.demo.redisson.bean.Student","name":"Jimmy","sex":"F","studentId":"001"}"

控制台输出

21:29:27.751  ... : rMap contains key '005' : false
21:29:27.802  ... : rMap size : 4
21:29:27.885  ... : rMap get value by '002' : Student{studentId='002', name='Jack', sex='M'}
21:29:27.937  ... : rMap put value in existed key '001' : Student{studentId='001', name='Jimmy', sex='F'}

RAtomicLong 和 RAtomicDouble

相当于 JUC 中原子类的增强版本,保证数据原子性。

    @Test
    public void testRAtomic() {
        RAtomicLong rAtomicLong = redissonClient.getAtomicLong("cache:atomic:long");
        // get
        logger.info("Init value : {}", rAtomicLong.get());
        // incrementAndGet
        logger.info("After increment value: {}", rAtomicLong.incrementAndGet());
        // addAndGet
        logger.info("After add value : {}", rAtomicLong.addAndGet(100L));

        RAtomicDouble rAtomicDouble = redissonClient.getAtomicDouble("cache:atomic:double");
        // set
        rAtomicDouble.set(10.0D);
        // get
        logger.info("Set value : {}", rAtomicDouble.get());
        // incrementAndGet
        logger.info("After increment value :{}", rAtomicDouble.incrementAndGet());
        // decrementAndGet
        logger.info("After decrement value :{}", rAtomicDouble.decrementAndGet());
        // compareAndSet
        rAtomicDouble.compareAndSet(10.0D, 13.3D);
        logger.info("After compare and set value :{}", rAtomicDouble.get());

        redissonClient.shutdown();
    }

控制台输出

21:38:15.822  ... : Init value : 202
21:38:15.868  ... : After increment value: 203
21:38:15.915  ... : After add value : 303
21:38:16.013  ... : Set value : 10.0
21:38:16.062  ... : After increment value :11.0
21:38:16.114  ... : After decrement value :10.0
21:38:16.215  ... : After compare and set value :13.3

RLock

Redisson 使用起来最爽的功能,通过参照其源码可以尝试自己去实现分布式锁功能。

    private static final String LOCK_NAME = "lock:001";

    @Test
    public void testLock() throws InterruptedException {
        RLock lock = redissonClient.getLock(LOCK_NAME);
        // 设置 10秒 后自动释放锁
        lock.lock(10,TimeUnit.SECONDS);
        logger.info("main acquired lock {}", LOCK_NAME);

        Thread thread = new Thread(() -> {
            RLock tLock = redissonClient.getLock(LOCK_NAME);
            tLock.lock();
            logger.info("thread acquired lock {}", LOCK_NAME);
            tLock.unlock();
            logger.info("thread released lock {}", LOCK_NAME);
        });

        thread.start();
        // 通过 unlock 手动释放锁
        lock.unlock();
        logger.info("main released lock {}", LOCK_NAME);
        thread.join();
        redissonClient.shutdown();
    }

控制台输出

21:42:08.274  ...       : main acquired lock lock:001
21:42:08.328  ...       : main released lock lock:001
21:42:08.332  ...       : thread acquired lock lock:001
21:42:08.385  ...       : thread released lock lock:001

6. 总结

本文可以说只是 Redisson 的冰山一角,还有很多强大的功能没有使用到,后续在使用过程中会对其进行逐步了解。

参照其源码的一些思想,对未使用 Redisson 的情境下也会有一定的帮助。

🚀 源码地址:https://github.com/NekoChips/SpringDemo/18.springboot-redisson


关于作者:NekoChips
本文地址:https://chenyangjie.com.cn/articles/2020/03/28/1585403311935.html
版权声明:本篇所有文章仅用于学习和技术交流,本作品采用 BY-NC-SA 4.0 许可协议,如需转载请注明出处!
许可协议:知识共享许可协议