更新时间:2024-10-24 GMT+08:00

Redisson客户端连接Redis(Java)

本章节介绍使用Redisson客户端连接Redis实例的方法。更多的客户端的使用方法请参考Redis客户端

在springboot类型的项目中,spring-data-redis中提供了对jedislettuce的适配,但没有提供对redisson组件的适配。为了能够在springboot中集成redisson,redisson侧主动提供了适配springboot的组件:redisson-spring-boot-starter(请参考:https://mvnrepository.com/artifact/org.redisson/redisson)。

注意:在springboot1.x中默认集成的是jedis,springboot2.x中改为了lettuce。

  • 如果创建Redis实例时设置了密码,使用Redisson客户端连接Redis时,需要配置密码进行连接,建议不要将明文密码硬编码在代码中。
  • 连接单机、读写分离、Proxy集群实例需要使用Redisson的SingleServerConfig配置对象中的useSingleServer方法,连接主备实例需要使用Redisson的MasterSlaveServersConfig配置对象中的useMasterSlaveServers方法,Cluster集群实例需要使用ClusterServersConfig对象中的useClusterServers方法。
  • Springboot版本不得低于2.3.12.RELEASE,Redisson版本不得低于3.37.0

前提条件

Pom配置

<!-- 引入spring-data-redis组件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <!-- 因springboot2.x中默认集成了lettuce,因此需要排掉该依赖 -->
        <exclusion>
            <artifactId>lettuce-core</artifactId>
            <groupId>io.lettuce</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 引入redisson对springboot的集成适配包 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>${redisson.version}</version>
</dependency>

基于Bean方式配置

因springboot中没有提供对redisson的适配,在application.properties配置文件自然也没有对应的配置项,只能通过基于Bean的方式注入。

  • 单机、读写分离、Proxy集群实例配置
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.codec.JsonJacksonCodec;
    import org.redisson.config.Config;
    import org.redisson.config.SingleServerConfig;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class SingleConfig {
    
        @Value("${redis.address:}")
        private String redisAddress;
    
        @Value("${redis.password:}")
        private String redisPassword;
    
        @Value("${redis.database:0}")
        private Integer redisDatabase = 0;
    
        @Value("${redis.connect.timeout:3000}")
        private Integer redisConnectTimeout = 3000;
    
        @Value("${redis.connection.idle.timeout:10000}")
        private Integer redisConnectionIdleTimeout = 10000;
    
        @Value("${redis.connection.ping.interval:1000}")
        private Integer redisConnectionPingInterval = 1000;
    
        @Value("${redis.timeout:2000}")
        private Integer timeout = 2000;
    
        @Value("${redis.connection.pool.min.size:50}")
        private Integer redisConnectionPoolMinSize;
    
        @Value("${redis.connection.pool.max.size:200}")
        private Integer redisConnectionPoolMaxSize;
    
        @Value("${redis.retry.attempts:3}")
        private Integer redisRetryAttempts = 3;
    
        @Value("${redis.retry.interval:200}")
        private Integer redisRetryInterval = 200;
    
        @Bean
        public RedissonClient redissonClient(){
            Config redissonConfig = new Config();
    
            SingleServerConfig serverConfig = redissonConfig.useSingleServer();
            serverConfig.setAddress(redisAddress);
            serverConfig.setConnectionMinimumIdleSize(redisConnectionPoolMinSize);
            serverConfig.setConnectionPoolSize(redisConnectionPoolMaxSize);
    
            serverConfig.setDatabase(redisDatabase);
            serverConfig.setPassword(redisPassword);
            serverConfig.setConnectTimeout(redisConnectTimeout);
            serverConfig.setIdleConnectionTimeout(redisConnectionIdleTimeout);
            serverConfig.setPingConnectionInterval(redisConnectionPingInterval);
            serverConfig.setTimeout(timeout);
            serverConfig.setRetryAttempts(redisRetryAttempts);
            serverConfig.setRetryInterval(redisRetryInterval);
    
            redissonConfig.setCodec(new JsonJacksonCodec());
            return Redisson.create(redissonConfig);
        }
    }
  • 主备实例配置
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.codec.JsonJacksonCodec;
    import org.redisson.config.Config;
    import org.redisson.config.MasterSlaveServersConfig;
    import org.redisson.config.ReadMode;
    import org.redisson.config.SubscriptionMode;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.HashSet;
    
    @Configuration
    public class MasterStandbyConfig {
        @Value("${redis.master.address}")
        private String redisMasterAddress;
    
        @Value("${redis.slave.address}")
        private String redisSlaveAddress;
    
        @Value("${redis.database:0}")
        private Integer redisDatabase = 0;
    
        @Value("${redis.password:}")
        private String redisPassword;
    
        @Value("${redis.connect.timeout:3000}")
        private Integer redisConnectTimeout = 3000;
    
        @Value("${redis.connection.idle.timeout:10000}")
        private Integer redisConnectionIdleTimeout = 10000;
    
        @Value("${redis.connection.ping.interval:1000}")
        private Integer redisConnectionPingInterval = 1000;
    
        @Value("${redis.timeout:2000}")
        private Integer timeout = 2000;
    
        @Value("${redis.master.connection.pool.min.size:50}")
        private Integer redisMasterConnectionPoolMinSize = 50;
    
        @Value("${redis.master.connection.pool.max.size:200}")
        private Integer redisMasterConnectionPoolMaxSize = 200;
    
        @Value("${redis.retry.attempts:3}")
        private Integer redisRetryAttempts = 3;
    
        @Value("${redis.retry.interval:200}")
        private Integer redisRetryInterval = 200;
    
        @Bean
        public RedissonClient redissonClient() {
            Config redissonConfig = new Config();
    
            MasterSlaveServersConfig serverConfig = redissonConfig.useMasterSlaveServers();
            serverConfig.setMasterAddress(redisMasterAddress);
            HashSet<String> slaveSet = new HashSet<>();
            slaveSet.add(redisSlaveAddress);
            serverConfig.setSlaveAddresses(slaveSet);
    
            serverConfig.setDatabase(redisDatabase);
            serverConfig.setPassword(redisPassword);
    
            serverConfig.setMasterConnectionMinimumIdleSize(redisMasterConnectionPoolMinSize);
            serverConfig.setMasterConnectionPoolSize(redisMasterConnectionPoolMaxSize);
    
            serverConfig.setReadMode(ReadMode.MASTER);
            serverConfig.setSubscriptionMode(SubscriptionMode.MASTER);
    
            serverConfig.setConnectTimeout(redisConnectTimeout);
            serverConfig.setIdleConnectionTimeout(redisConnectionIdleTimeout);
            serverConfig.setPingConnectionInterval(redisConnectionPingInterval);
            serverConfig.setTimeout(timeout);
            serverConfig.setRetryAttempts(redisRetryAttempts);
            serverConfig.setRetryInterval(redisRetryInterval);
    
            redissonConfig.setCodec(new JsonJacksonCodec());
            return Redisson.create(redissonConfig);
        }
    }
  • Cluster集群实例配置
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.codec.JsonJacksonCodec;
    import org.redisson.config.ClusterServersConfig;
    import org.redisson.config.Config;
    import org.redisson.config.ReadMode;
    import org.redisson.config.SubscriptionMode;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.List;
    
    @Configuration
    public class ClusterConfig {
    
        @Value("${redis.cluster.address}")
        private List<String> redisClusterAddress;
    
        @Value("${redis.cluster.scan.interval:5000}")
        private Integer redisClusterScanInterval = 5000;
    
        @Value("${redis.password:}")
        private String redisPassword;
    
        @Value("${redis.connect.timeout:3000}")
        private Integer redisConnectTimeout = 3000;
    
        @Value("${redis.connection.idle.timeout:10000}")
        private Integer redisConnectionIdleTimeout = 10000;
    
        @Value("${redis.connection.ping.interval:1000}")
        private Integer redisConnectionPingInterval = 1000;
    
        @Value("${redis.timeout:2000}")
        private Integer timeout = 2000;
    
        @Value("${redis.retry.attempts:3}")
        private Integer redisRetryAttempts = 3;
    
        @Value("${redis.retry.interval:200}")
        private Integer redisRetryInterval = 200;
    
        @Value("${redis.master.connection.pool.min.size:50}")
        private Integer redisMasterConnectionPoolMinSize = 50;
    
        @Value("${redis.master.connection.pool.max.size:200}")
        private Integer redisMasterConnectionPoolMaxSize = 200;
    
        @Bean
        public RedissonClient redissonClient() {
            Config redissonConfig = new Config();
    
            ClusterServersConfig serverConfig = redissonConfig.useClusterServers();
            serverConfig.setNodeAddresses(redisClusterAddress);
            serverConfig.setScanInterval(redisClusterScanInterval);
    
            serverConfig.setPassword(redisPassword);
    
            serverConfig.setMasterConnectionMinimumIdleSize(redisMasterConnectionPoolMinSize);
            serverConfig.setMasterConnectionPoolSize(redisMasterConnectionPoolMaxSize);
    
            serverConfig.setReadMode(ReadMode.MASTER);
            serverConfig.setSubscriptionMode(SubscriptionMode.MASTER);
    
            serverConfig.setConnectTimeout(redisConnectTimeout);
            serverConfig.setIdleConnectionTimeout(redisConnectionIdleTimeout);
            serverConfig.setPingConnectionInterval(redisConnectionPingInterval);
            serverConfig.setTimeout(timeout);
            serverConfig.setRetryAttempts(redisRetryAttempts);
            serverConfig.setRetryInterval(redisRetryInterval);
    
            redissonConfig.setCodec(new JsonJacksonCodec());
            return Redisson.create(redissonConfig);
        }
    }

SSL连接配置(可选配置)

当实例开启了SSL,通过SSL连接实例时,请将基于Bean方式配置中的RedissonClient构造方法clientConfiguration()中添加如下configRedissonSSL(serverConfig)逻辑,同时将redis的连接地址从redis://ip:port改为rediss://ip:port格式。Redis实例支持SSL的情况请参考配置Redis SSL数据加密传输

private void configRedissonSSL(BaseConfig serverConfig) {
    TrustManagerFactory trustManagerFactory = null;
    try {
        //加载自定义路径下的ca证书,可结合具体业务配置
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        Certificate ca;
        try (InputStream is = new FileInputStream(certificationPath)) {
            ca = cf.generateCertificate(is);
        }

        //创建keystore
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        //创建TrustManager
        trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);
    } catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException e) {
        e.printStackTrace();
        return;
    }

    serverConfig.setSslTrustManagerFactory(trustManagerFactory);
}

参数明细

表1 Config参数

参数

默认值

说明

codec

org.redisson.codec.JsonJacksonCodec

编码格式,内置了JSON/Avro/Smile/CBOR/MsgPack等编码格式

threads

cpu核数 * 2

RTopic Listener、RRemoteService和RExecutorService执行使用的线程池

executor

null

功能同上,不设置该参数时,会根据threads参数初始化一个线程池

nettyThreads

cpu核数 * 2

连接redis-server的tcp channel使用的线程池,所有channel共享该连接池,映射到netty即Bootstrap.group(...)

eventLoopGroup

null

功能同上,不设置该参数时,会根据nettyThreads参数初始化一个EventLoopGroup,用于底层tcpchannel使用

transportMode

TransportMode.NIO

传输模式,可选有NIO、EPOLL(需额外引包)、KQUEUE(需额外引包)

lockWatchdogTimeout

30000

监控锁的看门狗超时时间,单位:毫秒。用于分布式锁场景下未指定leaseTimeout参数时,采用该值为默认值

keepPubSubOrder

true

是否按照订阅发布消息的顺序来接收,如能接受并行处理消息,建议设置为false

表2 单机、读写分离、Proxy集群实例SingleServerConfig参数

参数

默认值

说明

address

-

节点连接信息,redis://ip:port

database

0

选择使用的数据库编号

connectionMinimumIdleSize

32

连接每个分片主节点的最小连接数

connectionPoolSize

64

连接每个分片主节点的最大连接数

subscriptionConnectionMinimumIdleSize

1

连接目标节点的用于发布订阅的最小连接数

subscriptionConnectionPoolSize

50

连接目标节点的用于发布订阅的最大连接数

subcriptionPerConnection

5

每个订阅连接上的最大订阅数量

connectionTimeout

10000

连接超时时间,单位:毫秒

idleConnectionTimeout

10000

空闲连接的最大回收时间,单位:毫秒

pingConnectionInterval

30000

检测连接可用心跳,单位:毫秒,建议值:3000ms

timeout

3000

请求等待响应的超时时间,单位:毫秒

retryAttempts

3

发送失败的最大重试次数

retryInterval

1500

每次重试的时间间隔,单位:毫秒,建议值:200ms

clientName

null

客户端名称

表3 主备实例MasterSlaveServersConfig参数

参数

默认值

说明

masterAddress

-

主节点连接信息,redis://ip:port。

slaveAddresses

-

从节点连接信息列表,Set<redis://ip:port>。

readMode

SLAVE

读取模式,默认读流量分发到从节点,可选值:MASTER、SLAVE、MASTER_SLAVE;建议MASTER,其余配置在故障切换场景下,均存在访问失败风险。

loadBalancer

RoundRobinLoadBalancer

负载均衡算法,在readMode为SLAVE、MASTER_SLAVE时生效,均衡读流量分发。

masterConnectionMinimumIdleSize

32

连接每个分片主节点的最小连接数。

masterConnectionPoolSize

64

连接每个分片主节点的最大连接数。

slaveConnectionMinimumIdleSize

32

连接每个分片每个从节点的最小连接数,如readMode=MASTER,该配置值将失效。

slaveConnectionPoolSize

64

连接每个分片每个从节点的最大连接数,如readMode=MASTER,该配置值将失效。

subscriptionMode

SLAVE

订阅模式,默认只在从节点订阅,可选值:SLAVE、MASTER;建议采用MASTER

subscriptionConnectionMinimumIdleSize

1

连接目标节点的用于发布订阅的最小连接数。

subscriptionConnectionPoolSize

50

连接目标节点的用于发布订阅的最大连接数。

subcriptionPerConnection

5

每个订阅连接上的最大订阅数量。

connectionTimeout

10000

连接超时时间,单位:毫秒。

idleConnectionTimeout

10000

空闲连接的最大回收时间,单位:毫秒。

pingConnectionInterval

30000

检测连接可用心跳,单位:毫秒,建议值:3000ms

timeout

3000

请求等待响应的超时时间,单位:毫秒。

retryAttempts

3

发送失败的最大重试次数。

retryInterval

1500

每次重试的时间间隔,单位:毫秒,建议值:200ms

clientName

null

客户端名称。

表4 Cluster集群实例ClusterServersConfig参数

参数

默认值

说明

nodeAddress

-

集群节点的地址连接信息,每个节点采用redis://ip:port方式,多个节点连接信息用英文逗号隔开。

password

null

集群登录密码。

scanInterval

1000

定时检测集群节点状态的时间间隔,单位:毫秒。

readMode

SLAVE

读取模式,默认读流量分发到从节点,可选值:MASTER、SLAVE、MASTER_SLAVE;建议修改为MASTER,其余配置在故障切换场景下,均存在访问失败风险。

loadBalancer

RoundRobinLoadBalancer

负载均衡算法,在readMode为SLAVE、MASTER_SLAVE时生效,均衡读流量分发。

masterConnectionMinimumIdleSize

32

连接每个分片主节点的最小连接数。

masterConnectionPoolSize

64

连接每个分片主节点的最大连接数。

slaveConnectionMinimumIdleSize

32

连接每个分片每个从节点的最小连接数,如readMode=MASTER,该配置值将失效。

slaveConnectionPoolSize

64

连接每个分片每个从节点的最大连接数,如readMode=MASTER,该配置值将失效。

subscriptionMode

SLAVE

订阅模式,默认只在从节点订阅,可选值:SLAVE、MASTER;建议采用MASTER

subscriptionConnectionMinimumIdleSize

1

连接目标节点的用于发布订阅的最小连接数。

subscriptionConnectionPoolSize

50

连接目标节点的用于发布订阅的最大连接数。

subcriptionPerConnection

5

每个订阅连接上的最大订阅数量。

connectionTimeout

10000

连接超时时间,单位:毫秒。

idleConnectionTimeout

10000

空闲连接的最大回收时间,单位:毫秒。

pingConnectionInterval

30000

检测连接可用心跳,单位:毫秒,建议值:3000

timeout

3000

请求等待响应的超时时间,单位:毫秒。

retryAttempts

3

发送失败的最大重试次数。

retryInterval

1500

每次重试的时间间隔,单位:毫秒,建议值:200

clientName

null

客户端名称。

DCS实例配置建议

  • 读取模式(readMode)

    建议采用MASTER,即Master节点承担所有的读写流量,一方面避免数据因主从同步时延带来的一致性问题;另一方面,如果从节点故障,配置值=SLAVE,所有读请求会触发报错;配置值=MASTER_SLAVE,部分读请求会触发异常。读报错会持续failedSlaveCheckInterval(默认180s)时间,直至从可用节点列表中摘除。

    如需读写流量分流处理,DCS服务提供了针对读写流量分流的读写分离实例类型,通过在中间架设代理节点实现读写流量分发,遇到从节点故障时,自动切流至主节点,对业务应用无感知,且故障感知时间窗口远小于redisson内部的时间窗口。

  • 订阅模式(subscriptionMode)

    建议采用MASTER,原理同读取模式(readMode)

  • 连接池配置

    以下计算方式只适用于一般业务场景,建议根据业务情况做适当调整适配。

    连接池的大小没有绝对的标准,建议根据业务流量进行合理配置,一般连接池大小的参数计算公式如下:

    • 最小连接数 =(单机访问Redis QPS)/(1000ms / 单命令平均耗时)
    • 最大连接数 =(单机访问Redis QPS)/(1000ms / 单命令平均耗时)* 150%

    举例:某个业务应用的QPS为10000左右,每个请求需访问Redis10次,即每秒对Redis的访问次数为100000次,同时该业务应用有10台机器,计算如下:

    单机访问Redis QPS = 100000 / 10 = 10000

    单命令平均耗时 = 20ms(Redis处理单命令耗时为5~10ms,遇到网络抖动按照15~20ms来估算)

    最小连接数 =(10000)/(1000ms / 20ms)= 200

    最大连接数 =(10000)/(1000ms / 20ms)* 150% = 300

  • 重试配置

    redisson中支持重试配置,主要是如下两个参数,建议根据业务情况配置合理值,一般重试次数为3,重试间隔为200ms左右。

    • retryAttempts:配置重试次数
    • retryInterval:配置重试时间间隔

在redisson中,部分API通过借助LUA的方式实现,性能表现上偏低,建议使用jedis客户端替换redisson。