
# Lettuce客户端连接Redis（Java）
本章节介绍使用Lettuce客户端连接Redis实例的方法。更多的客户端的使用方法请参考[Redis客户端](https://redis.io/clients)。
在Springboot类型的项目中，spring-data-redis中已提供了对[jedis](https://github.com/redis/jedis)、[lettuce](https://github.com/lettuce-io/lettuce-core)的集成适配。另外，在Springboot 1.x中默认集成的是jedis，Springboot 2.x中改为了lettuce，因此在Springboot 2.x及更高版本中想集成使用lettuce，无需手动引入lettuce依赖包。
#### 约束与限制
Springboot版本不得低于2.3.12.RELEASE，Lettuce版本不得低于[6.3.0.RELEASE](https://github.com/redis/lettuce/releases/tag/6.3.0.RELEASE)，netty版本要求4.1.100.Final及以上。
#### 前提条件
- 已成功创建Redis实例，且状态为"运行中"。创建Redis实例的操作请参考[购买Redis实例](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-0713002.html)。
- 查看并获取待连接Redis实例的IP地址/域名和端口。具体步骤请参见[查看和修改DCS实例基本信息](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-0312016.html)。
- 连接实例前确保客户端与Redis实例之间网络互通，具体请参考[连接Redis网络要求](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-0312007.html)。
 
#### Pom配置
```
<!-- 引入spring-data-redis组件，默认已集成Lettuce依赖SDK -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>io.lettuce</groupId>
   <artifactId>lettuce-core</artifactId>
   <version>6.3.0.RELEASE</version>
</dependency>
<dependency>
   <groupId>io.netty</groupId>
   <artifactId>netty-transport-native-epoll</artifactId>
   <version>4.1.100.Final</version>
   <classifier>linux-x86_64</classifier>
</dependency>
```
#### 基于application.properties配置
Springboot版本不同，配置也不同。Springboot 2.x和Springboot 3.x的配置参考如下。
- [Springboot 2.x配置]
- [Springboot 3.x配置]
- 单机、主备、读写分离、Proxy集群实例配置
  ```
  #redis host
  spring.redis.host=<host>
  #redis 端口号
  spring.redis.port=<port>
  #redis 数据库下标
  spring.redis.database=0
  #redis 密码
  spring.redis.password=<password>
  #redis 读写超时
  spring.redis.timeout=2000
  ```
  
- Cluster集群实例配置
  ```
  # redis cluster节点信息
  spring.redis.cluster.nodes=<ip:port>,<ip:port>,<ip:port>
  # redis cluster 最大重定向次数
  spring.redis.cluster.max-redirects=3
  # redis cluster 节点密码
  spring.redis.password=<password>
  # redis cluster 超时配置
  spring.redis.timeout=2000
  # 开启自适应拓扑刷新
  spring.redis.lettuce.cluster.refresh.adaptive=true
  # 开启每10S定时刷新拓扑结构
  spring.redis.lettuce.cluster.refresh.period=10S
  ```
  
 
- 单机、主备、读写分离、Proxy集群实例配置
  ```
  #redis host
  spring.data.redis.host=<host>
  #redis 端口号
  spring.data.redis.port=<port>
  #redis 数据库下标
  spring.data.redis.database=0
  #redis 密码
  spring.data.redis.password=<password>
  #redis 读写超时
  spring.data.redis.timeout=2000
  ```
  
- Cluster集群实例配置
  ```
  # redis cluster节点信息
  spring.data.redis.cluster.nodes=<ip:port>,<ip:port>,<ip:port>
  # redis cluster 最大重定向次数
  spring.data.redis.cluster.max-redirects=3
  # redis cluster 节点密码
  spring.data.redis.password=<password>
  # redis cluster 超时配置
  spring.data.redis.timeout=2000
  # 开启自适应拓扑刷新
  spring.data.redis.lettuce.cluster.refresh.adaptive=true
  # 开启每10S定时刷新拓扑结构
  spring.data.redis.lettuce.cluster.refresh.period=10S
  ```
  
 
 #### 基于Bean方式配置
- 单机、主备、读写分离、Proxy集群实例配置
  ```
  import java.time.Duration;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.data.redis.connection.RedisConnectionFactory;
  import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
  import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
  import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
  import io.lettuce.core.ClientOptions;
  import io.lettuce.core.SocketOptions;
  /**
  * Lettuce 非池化配置，与 application.properties 配置方式二选一
  */
  @Configuration
  public class RedisConfiguration {
      @Value("${redis.host}")
      private String redisHost;
      @Value("${redis.port:6379}")
      private Integer redisPort = 6379;
      @Value("${redis.database:0}")
      private Integer redisDatabase = 0;
      @Value("${redis.password:}")
      private String redisPassword;
      @Value("${redis.connect.timeout:2000}")
      private Integer redisConnectTimeout = 2000;
      @Value("${redis.read.timeout:2000}")
      private Integer redisReadTimeout = 2000;
      /**
       *  TCP_KEEPALIVE 配置参数:
       *  两次 keepalive 间的时间间隔 = TCP_KEEPALIVE_TIME = 30
       *  连接空闲多久开始 keepalive = TCP_KEEPALIVE_TIME/3 = 10
       *  keepalive 几次之后断开连接 = TCP_KEEPALIVE_COUNT = 3
       */
      private static final int TCP_KEEPALIVE_TIME = 30;
      /**
       * TCP_USER_TIMEOUT 连接空闲限制时间，解决Lettuce长时间超时问题。
       * refer: https://github.com/lettuce-io/lettuce-core/issues/2082
       */
      private static final int TCP_USER_TIMEOUT = 30;
      @Bean
      public RedisConnectionFactory redisConnectionFactory(LettuceClientConfiguration clientConfiguration) {
          RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration();
          standaloneConfiguration.setHostName(redisHost);
          standaloneConfiguration.setPort(redisPort);
          standaloneConfiguration.setDatabase(redisDatabase);
          standaloneConfiguration.setPassword(redisPassword);
          LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(standaloneConfiguration, clientConfiguration);
          connectionFactory.setDatabase(redisDatabase);
          return connectionFactory;
      }
      @Bean
      public LettuceClientConfiguration clientConfiguration() {
          
          SocketOptions socketOptions = SocketOptions.builder()
              .keepAlive(SocketOptions.KeepAliveOptions.builder()
                  // 两次 keepalive 间的时间间隔
                  .idle(Duration.ofSeconds(TCP_KEEPALIVE_TIME))
                  // 连接空闲多久开始 keepalive
                  .interval(Duration.ofSeconds(TCP_KEEPALIVE_TIME/3))
                  // keepalive 几次之后断开连接
                  .count(3)
                  // 是否开启保活连接
                  .enable()
                  .build())
              .tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
                  // 解决服务端rst导致的长时间超时问题
                  .tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
                  .enable()
                  .build())
              // tcp 连接超时设置
              .connectTimeout(Duration.ofMillis(redisConnectTimeout))
              .build();
          ClientOptions clientOptions = ClientOptions.builder()
                  .autoReconnect(true)
                  .pingBeforeActivateConnection(true)
                  .cancelCommandsOnReconnectFailure(false)
                  .disconnectedBehavior(ClientOptions.DisconnectedBehavior.ACCEPT_COMMANDS)
                  .socketOptions(socketOptions)
                  .build();
          
          LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
                  .commandTimeout(Duration.ofMillis(redisReadTimeout))
                  // Proxy集群实例无需设置readFrom
                  .readFrom(ReadFrom.MASTER)
                  .clientOptions(clientOptions)
                  .build();
          return clientConfiguration;
      }
  }
  ```
  
- 单机、主备、读写分离、Proxy集群实例池化配置
  引入池化组件
  ```
  <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
      <version>2.11.1</version>
  </dependency>
  ```
  代码配置
  ```
  import java.time.Duration;
  import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.data.redis.connection.RedisConnectionFactory;
  import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
  import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
  import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
  import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
  import io.lettuce.core.ClientOptions;
  import io.lettuce.core.SocketOptions;
  /**
  * Lettuce 池化配置
  */
  @Configuration
  public class RedisPoolConfiguration {
      @Value("${redis.host}")
      private String redisHost;
      @Value("${redis.port:6379}")
      private Integer redisPort = 6379;
      @Value("${redis.database:0}")
      private Integer redisDatabase = 0;
      @Value("${redis.password:}")
      private String redisPassword;
      @Value("${redis.connect.timeout:2000}")
      private Integer redisConnectTimeout = 2000;
      @Value("${redis.read.timeout:2000}")
      private Integer redisReadTimeout = 2000;
      @Value("${redis.pool.minSize:50}")
      private Integer redisPoolMinSize = 50;
      @Value("${redis.pool.maxSize:200}")
      private Integer redisPoolMaxSize = 200;
      @Value("${redis.pool.maxWaitMillis:2000}")
      private Integer redisPoolMaxWaitMillis = 2000;
      @Value("${redis.pool.softMinEvictableIdleTimeMillis:1800000}")
      private Integer redisPoolSoftMinEvictableIdleTimeMillis = 30 * 60 * 1000;
      @Value("${redis.pool.timeBetweenEvictionRunsMillis:60000}")
      private Integer redisPoolBetweenEvictionRunsMillis = 60 * 1000;
      /**
       *  TCP_KEEPALIVE 配置参数:
       *  两次 keepalive 间的时间间隔 = TCP_KEEPALIVE_TIME = 30
       *  连接空闲多久开始 keepalive = TCP_KEEPALIVE_TIME/3 = 10
       *  keepalive 几次之后断开连接 = TCP_KEEPALIVE_COUNT = 3
       */
      private static final int TCP_KEEPALIVE_TIME = 30;
      /**
       * TCP_USER_TIMEOUT 连接空闲限制时间，解决Lettuce长时间超时问题。
       * refer: https://github.com/lettuce-io/lettuce-core/issues/2082
       */
      private static final int TCP_USER_TIMEOUT = 30;
      @Bean
      public RedisConnectionFactory redisConnectionFactory(LettuceClientConfiguration clientConfiguration) {
          RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration();
          standaloneConfiguration.setHostName(redisHost);
          standaloneConfiguration.setPort(redisPort);
          standaloneConfiguration.setDatabase(redisDatabase);
          standaloneConfiguration.setPassword(redisPassword);
          LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(standaloneConfiguration, clientConfiguration);
          connectionFactory.setDatabase(redisDatabase);
          //关闭共享链接，才能池化生效
          connectionFactory.setShareNativeConnection(false);
          return connectionFactory;
      }
      @Bean
      public LettuceClientConfiguration clientConfiguration() {
          
          SocketOptions socketOptions = SocketOptions.builder()
              .keepAlive(SocketOptions.KeepAliveOptions.builder()
                  // 两次 keepalive 间的时间间隔
                  .idle(Duration.ofSeconds(TCP_KEEPALIVE_TIME))
                  // 连接空闲多久开始 keepalive
                  .interval(Duration.ofSeconds(TCP_KEEPALIVE_TIME/3))
                  // keepalive 几次之后断开连接
                  .count(3)
                  // 是否开启保活连接
                  .enable()
                  .build())
              .tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
                  // 解决服务端rst导致的长时间超时问题
                  .tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
                  .enable()
                  .build())
              // tcp 连接超时设置
              .connectTimeout(Duration.ofMillis(redisConnectTimeout))
              .build();
          ClientOptions clientOptions = ClientOptions.builder()
                  .autoReconnect(true)
                  .pingBeforeActivateConnection(true)
                  .cancelCommandsOnReconnectFailure(false)
                  .disconnectedBehavior(ClientOptions.DisconnectedBehavior.ACCEPT_COMMANDS)
                  .socketOptions(socketOptions)
                  .build();
          LettucePoolingClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder()
                  .poolConfig(poolConfig())
                  .commandTimeout(Duration.ofMillis(redisReadTimeout))
                  .clientOptions(clientOptions)
                  // Proxy集群实例无需设置readFrom
                  .readFrom(ReadFrom.MASTER)
                  .build();
          return clientConfiguration;
      }
      private GenericObjectPoolConfig redisPoolConfig() {
          GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
          //连接池的最小连接数
          poolConfig.setMinIdle(redisPoolMinSize);
          //连接池的最大空闲连接数
          poolConfig.setMaxIdle(redisPoolMaxSize);
          //连接池的最大连接数
          poolConfig.setMaxTotal(redisPoolMaxSize);
          //连接池耗尽后是否需要等待，默认true表示等待。当值为true时，setMaxWait才会生效
          poolConfig.setBlockWhenExhausted(true);
          //连接池耗尽后获取连接的最大等待时间，默认-1表示一直等待
          poolConfig.setMaxWait(Duration.ofMillis(redisPoolMaxWaitMillis));
          //创建连接时校验有效性(ping)，默认false
          poolConfig.setTestOnCreate(false);
          //获取连接时校验有效性(ping)，默认false，业务量大时建议设置为false减少开销
          poolConfig.setTestOnBorrow(true);
          //归还连接时校验有效性(ping)，默认false，业务量大时建议设置为false减少开销
          poolConfig.setTestOnReturn(false);
          //是否开启空闲连接检测，如为false，则不剔除空闲连接
          poolConfig.setTestWhileIdle(true);
          //连接空闲多久后逐出，当空闲时间>该值，并且空闲连接数>最小空闲连接数时直接逐出
          poolConfig.setSoftMinEvictableIdleTime(Duration.ofMillis(redisPoolSoftMinEvictableIdleTimeMillis));
          //关闭根据MinEvictableIdleTimeMillis判断逐出
          poolConfig.setMinEvictableIdleTime(Duration.ofMillis(-1));
          //空闲连接逐出的检测周期，默认为60s
          poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(redisPoolBetweenEvictionRunsMillis));
          return poolConfig;
      }
  }
  ```
  

- Cluster集群实例配置
  ```
  import java.time.Duration;
  import java.util.ArrayList;
  import java.util.List;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.data.redis.connection.RedisClusterConfiguration;
  import org.springframework.data.redis.connection.RedisConnectionFactory;
  import org.springframework.data.redis.connection.RedisNode;
  import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
  import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
  import io.lettuce.core.ClientOptions;
  import io.lettuce.core.SocketOptions;
  import io.lettuce.core.cluster.ClusterClientOptions;
  import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
  /**
  * Lettuce Cluster 非池化配置，与 application.properties 配置方式二选一
  */
  @Configuration
  public class RedisConfiguration {
      @Value("${redis.cluster.nodes}")
      private String redisClusterNodes;
      @Value("${redis.cluster.maxDirects:3}")
      private Integer redisClusterMaxDirects;
      @Value("${redis.password:}")
      private String redisPassword;
      @Value("${redis.connect.timeout:2000}")
      private Integer redisConnectTimeout = 2000;
      @Value("${redis.read.timeout:2000}")
      private Integer redisReadTimeout = 2000;
      @Value("${redis.cluster.topology.refresh.period.millis:10000}")
      private Integer redisClusterTopologyRefreshPeriodMillis = 10000;
      /**
       *  TCP_KEEPALIVE 配置参数:
       *  两次 keepalive 间的时间间隔 = TCP_KEEPALIVE_TIME = 30
       *  连接空闲多久开始 keepalive = TCP_KEEPALIVE_TIME/3 = 10
       *  keepalive 几次之后断开连接 = TCP_KEEPALIVE_COUNT = 3
       */
      private static final int TCP_KEEPALIVE_TIME = 30;
      /**
       * TCP_USER_TIMEOUT 连接空闲限制时间，解决Lettuce长时间超时问题。
       * refer: https://github.com/lettuce-io/lettuce-core/issues/2082
       */
      private static final int TCP_USER_TIMEOUT = 30;
      @Bean
      public RedisConnectionFactory redisConnectionFactory(LettuceClientConfiguration clientConfiguration) {
          RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
          List<RedisNode> clusterNodes = new ArrayList<>();
          for (String clusterNodeStr : redisClusterNodes.split(",")) {
              String[] nodeInfo = clusterNodeStr.split(":");
              clusterNodes.add(new RedisNode(nodeInfo[0], Integer.valueOf(nodeInfo[1])));
          }
          clusterConfiguration.setClusterNodes(clusterNodes);
          clusterConfiguration.setPassword(redisPassword);
          clusterConfiguration.setMaxRedirects(redisClusterMaxDirects);
          LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(clusterConfiguration, clientConfiguration);
          return connectionFactory;
      }
      @Bean
      public LettuceClientConfiguration clientConfiguration() {
          SocketOptions socketOptions = SocketOptions.builder()
              .keepAlive(SocketOptions.KeepAliveOptions.builder()
                  // 两次 keepalive 间的时间间隔
                  .idle(Duration.ofSeconds(TCP_KEEPALIVE_TIME))
                  // 连接空闲多久开始 keepalive
                  .interval(Duration.ofSeconds(TCP_KEEPALIVE_TIME/3))
                  // keepalive 几次之后断开连接
                  .count(3)
                  // 是否开启保活连接
                  .enable()
                  .build())
              .tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
                  // 解决服务端rst导致的长时间超时问题
                  .tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
                  .enable()
                  .build())
              // tcp 连接超时设置
              .connectTimeout(Duration.ofMillis(redisConnectTimeout))
              .build();
          ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                  .enableAllAdaptiveRefreshTriggers()
                  .enablePeriodicRefresh(Duration.ofMillis(redisClusterTopologyRefreshPeriodMillis))
                  .build();
          ClusterClientOptions clientOptions = ClusterClientOptions.builder()
                  .autoReconnect(true)
                  .pingBeforeActivateConnection(true)
                  .cancelCommandsOnReconnectFailure(false)
                  .disconnectedBehavior(ClientOptions.DisconnectedBehavior.ACCEPT_COMMANDS)
                  .socketOptions(socketOptions)
                  .topologyRefreshOptions(topologyRefreshOptions)
                  .build();
          LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
                  .commandTimeout(Duration.ofMillis(redisReadTimeout))
                  .readFrom(ReadFrom.MASTER)
                  .clientOptions(clientOptions)
                  .build();
          return clientConfiguration;
      }
  }
  ```
  
- Cluster实例池化配置
  引入池化组件
  ```
  <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
      <version>2.11.1</version>
  </dependency>
  ```
  代码配置
  ```
  import java.time.Duration;
  import java.util.ArrayList;
  import java.util.List;
  import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.data.redis.connection.RedisClusterConfiguration;
  import org.springframework.data.redis.connection.RedisConnectionFactory;
  import org.springframework.data.redis.connection.RedisNode;
  import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
  import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
  import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
  import io.lettuce.core.ClientOptions;
  import io.lettuce.core.SocketOptions;
  import io.lettuce.core.cluster.ClusterClientOptions;
  import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
  /**
  * Lettuce 池化配置
  */
  @Configuration
  public class RedisPoolConfiguration {
      @Value("${redis.cluster.nodes}")
      private String redisClusterNodes;
      @Value("${redis.cluster.maxDirects:3}")
      private Integer redisClusterMaxDirects;
      @Value("${redis.password:}")
      private String redisPassword;
      @Value("${redis.connect.timeout:2000}")
      private Integer redisConnectTimeout = 2000;
      @Value("${redis.read.timeout:2000}")
      private Integer redisReadTimeout = 2000;
      @Value("${redis.cluster.topology.refresh.period.millis:10000}")
      private Integer redisClusterTopologyRefreshPeriodMillis = 10000;
      @Value("${redis.pool.minSize:50}")
      private Integer redisPoolMinSize = 50;
      @Value("${redis.pool.maxSize:200}")
      private Integer redisPoolMaxSize = 200;
      @Value("${redis.pool.maxWaitMillis:2000}")
      private Integer redisPoolMaxWaitMillis = 2000;
      @Value("${redis.pool.softMinEvictableIdleTimeMillis:1800000}")
      private Integer redisPoolSoftMinEvictableIdleTimeMillis = 30 * 60 * 1000;
      @Value("${redis.pool.timeBetweenEvictionRunsMillis:60000}")
      private Integer redisPoolBetweenEvictionRunsMillis = 60 * 1000;
      /**
       *  TCP_KEEPALIVE 配置参数:
       *  两次 keepalive 间的时间间隔 = TCP_KEEPALIVE_TIME = 30
       *  连接空闲多久开始 keepalive = TCP_KEEPALIVE_TIME/3 = 10
       *  keepalive 几次之后断开连接 = TCP_KEEPALIVE_COUNT = 3
       */
      private static final int TCP_KEEPALIVE_TIME = 30;
      /**
       * TCP_USER_TIMEOUT 连接空闲限制时间，解决Lettuce长时间超时问题。
       * refer: https://github.com/lettuce-io/lettuce-core/issues/2082
       */
      private static final int TCP_USER_TIMEOUT = 30;
      @Bean
      public RedisConnectionFactory redisConnectionFactory(LettuceClientConfiguration clientConfiguration) {
          RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
          List<RedisNode> clusterNodes = new ArrayList<>();
          for (String clusterNodeStr : redisClusterNodes.split(",")) {
              String[] nodeInfo = clusterNodeStr.split(":");
              clusterNodes.add(new RedisNode(nodeInfo[0], Integer.valueOf(nodeInfo[1])));
          }
          clusterConfiguration.setClusterNodes(clusterNodes);
          clusterConfiguration.setPassword(redisPassword);
          clusterConfiguration.setMaxRedirects(redisClusterMaxDirects);
          LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(clusterConfiguration, clientConfiguration);
          //一定要关闭共享连接，否则连接池将不会生效
          connectionFactory.setShareNativeConnection(false);
          return connectionFactory;
      }
      @Bean
      public LettuceClientConfiguration clientConfiguration() {
          SocketOptions socketOptions = SocketOptions.builder()
              .keepAlive(SocketOptions.KeepAliveOptions.builder()
                  // 两次 keepalive 间的时间间隔
                  .idle(Duration.ofSeconds(TCP_KEEPALIVE_TIME))
                  // 连接空闲多久开始 keepalive
                  .interval(Duration.ofSeconds(TCP_KEEPALIVE_TIME/3))
                  // keepalive 几次之后断开连接
                  .count(3)
                  // 是否开启保活连接
                  .enable()
                  .build())
              .tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
                  // 解决服务端rst导致的长时间超时问题
                  .tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
                  .enable()
                  .build())
              // tcp 连接超时设置
              .connectTimeout(Duration.ofMillis(redisConnectTimeout))
              .build();
          ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                  .enableAllAdaptiveRefreshTriggers()
                  .enablePeriodicRefresh(Duration.ofMillis(redisClusterTopologyRefreshPeriodMillis))
                  .build();
          ClusterClientOptions clientOptions = ClusterClientOptions.builder()
                  .autoReconnect(true)
                  .pingBeforeActivateConnection(true)
                  .cancelCommandsOnReconnectFailure(false)
                  .disconnectedBehavior(ClientOptions.DisconnectedBehavior.ACCEPT_COMMANDS)
                  .socketOptions(socketOptions)
                  .topologyRefreshOptions(topologyRefreshOptions)
                  .build();
          LettucePoolingClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder()
                  .poolConfig(poolConfig())
                  .commandTimeout(Duration.ofMillis(redisReadTimeout))
                  .clientOptions(clientOptions)
                  .readFrom(ReadFrom.MASTER)
                  .build();
          return clientConfiguration;
      }
      private GenericObjectPoolConfig poolConfig() {
          GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
          //连接池的最小连接数
          poolConfig.setMinIdle(redisPoolMinSize);
          //连接池的最大空闲连接数
          poolConfig.setMaxIdle(redisPoolMaxSize);
          //连接池的最大连接数
          poolConfig.setMaxTotal(redisPoolMaxSize);
          //连接池耗尽后是否需要等待, 默认true表示等待. 当值为true时, setMaxWait才会生效
          poolConfig.setBlockWhenExhausted(true);
          //连接池耗尽后获取连接的最大等待时间, 默认-1表示一直等待
          poolConfig.setMaxWait(Duration.ofMillis(redisPoolMaxWaitMillis));
          //创建连接时校验有效性(ping)，默认false
          poolConfig.setTestOnCreate(false);
          //获取连接时校验有效性(ping), 默认false, 业务量大时建议设置为false减少开销
          poolConfig.setTestOnBorrow(true);
          //归还连接时校验有效性(ping)，默认false, 业务量大时建议设置为false减少开销
          poolConfig.setTestOnReturn(false);
          //是否开启空闲连接检测，如为false，则不剔除空闲连接
          poolConfig.setTestWhileIdle(true);
          //禁止最小空闲时间关闭连接
          poolConfig.setMinEvictableIdleTime(Duration.ofMillis(-1));
          //连接空闲多久后逐出，当空闲时间>该值，并且空闲连接数>最小空闲连接数时直接逐出，不再根据MinEvictableIdleTimeMillis判断（默认逐出策略）
          poolConfig.setSoftMinEvictableIdleTime(Duration.ofMillis(redisPoolSoftMinEvictableIdleTimeMillis));
          //空闲连接逐出的检测周期，默认为60s
          poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(redisPoolBetweenEvictionRunsMillis));
          return poolConfig;
      }
  }
  ```
  
 
#### SSL连接配置（可选配置）
当实例开启了SSL，通过SSL连接实例时，请使用以下内容替换[基于Bean方式配置]中的LettuceClientConfiguration构造方法clientConfiguration()。Redis实例支持SSL的情况请参考[配置Redis SSL数据加密传输](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-023129.html)。
- 单机、主备、读写分离、Proxy集群实例配置
  ```
  @Bean
  public LettuceClientConfiguration clientConfiguration() {
      SocketOptions socketOptions = SocketOptions.builder()
              .keepAlive(SocketOptions.KeepAliveOptions.builder()
                  // 两次 keepalive 间的时间间隔
                  .idle(Duration.ofSeconds(TCP_KEEPALIVE_TIME))
                  // 连接空闲多久开始 keepalive
                  .interval(Duration.ofSeconds(TCP_KEEPALIVE_TIME/3))
                  // keepalive 几次之后断开连接
                  .count(3)
                  // 是否开启保活连接
                  .enable()
                  .build())
              .tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
                  // 解决服务端rst导致的长时间超时问题
                  .tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
                  .enable()
                  .build())
              // tcp 连接超时设置
              .connectTimeout(Duration.ofMillis(redisConnectTimeout))
              .build();
      SslOptions sslOptions = SslOptions.builder()
          .trustManager(new File(certificationPath))
          .build();
      ClientOptions clientOptions = ClientOptions.builder()
          .sslOptions(sslOptions)
          .autoReconnect(true)
          .pingBeforeActivateConnection(true)
          .cancelCommandsOnReconnectFailure(false)
          .disconnectedBehavior(ClientOptions.DisconnectedBehavior.ACCEPT_COMMANDS)
          .socketOptions(socketOptions)
          .build();
      LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
          .commandTimeout(Duration.ofMillis(redisReadTimeout))
          // Proxy集群实例无需设置readFrom
          .readFrom(ReadFrom.MASTER)
          .clientOptions(clientOptions)
          .useSsl()
          .build();
      return clientConfiguration;
  }
  ```
  
- Cluster集群实例配置
  ```
  @Bean
  public LettuceClientConfiguration clientConfiguration() {
      SocketOptions socketOptions = SocketOptions.builder()
              .keepAlive(SocketOptions.KeepAliveOptions.builder()
                  // 两次 keepalive 间的时间间隔
                  .idle(Duration.ofSeconds(TCP_KEEPALIVE_TIME))
                  // 连接空闲多久开始 keepalive
                  .interval(Duration.ofSeconds(TCP_KEEPALIVE_TIME/3))
                  // keepalive 几次之后断开连接
                  .count(3)
                  // 是否开启保活连接
                  .enable()
                  .build())
              .tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
                  // 解决服务端rst导致的长时间超时问题
                  .tcpUserTimeout(Duration.ofSeconds(TCP_USER_TIMEOUT))
                  .enable()
                  .build())
              // tcp 连接超时设置
              .connectTimeout(Duration.ofMillis(redisConnectTimeout))
              .build();
      SslOptions sslOptions = SslOptions.builder()
          .trustManager(new File(certificationPath))
          .build();
      ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
          .enableAllAdaptiveRefreshTriggers()
          .enablePeriodicRefresh(Duration.ofMillis(redisClusterTopologyRefreshPeriodMillis))
          .build();
      ClusterClientOptions clientOptions = ClusterClientOptions.builder()
          .sslOptions(sslOptions)
          .autoReconnect(true)
          .pingBeforeActivateConnection(true)
          .cancelCommandsOnReconnectFailure(false)
          .disconnectedBehavior(ClientOptions.DisconnectedBehavior.ACCEPT_COMMANDS)
          .socketOptions(socketOptions)
          .topologyRefreshOptions(topologyRefreshOptions)
          .build();
      LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
          .commandTimeout(Duration.ofMillis(redisReadTimeout))
          .readFrom(ReadFrom.MASTER)
          .clientOptions(clientOptions)
          .useSsl()
          .build();
      return clientConfiguration;
  }
  ```
  
 
#### 参数明细
表1LettuceConnectionFactory参数 
| 参数                    | 类型                         | 默认值  | 说明                                                                                                                                                                                                                                                                                                   |
|:---|:---|:---|:---|
| configuration         | RedisConfiguration         | -    | redis连接配置，常用两个子类： - RedisStandaloneConfiguration  - RedisClusterConfiguration   |
| clientConfiguration   | LettuceClientConfiguration | -    | 客户端配置参数，常用子类： LettucePoolingClientConfiguration（用于池化）。                                                                                                                                                                                                           |
| shareNativeConnection | boolean                    | true | 是否采用共享连接，默认true，**采用连接池时必须设置为false**。                                                                                                                                                                                                                                                                |
   
表2RedisStandaloneConfiguration参数 
| 参数       | 默认值       | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
|:---|:---|:---|
| hostName | localhost | 连接Redis实例的IP地址/域名。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| port     | 6379      | 连接端口号。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| database | 0         | 数据库下标。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| password | -         | 连接Redis实例的密码。如果实例已开启免密访问，无需输入实例的访问密码**。** 忘记密码或需要重置密码请参见[重置缓存实例密码](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-0312039.html#dcs-ug-0312039__section7753521111)。 - 如果使用创建Redis实例时自定义的密码（即实例默认账号的密码），请将其修改为实际密码。  - 如果使用实例创建的ACL账号连接，实例的密码需要配置为"账号名称:账号密码"，即**{username:password}** 。创建或查看ACL账号，请参见[配置Redis ACL访问账号](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-221220.html)。   |
   
表3RedisClusterConfiguration参数 
| 参数           | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|:---|:---|
| clusterNodes | cluster节点连接信息，需节点IP、Port。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| maxRedirects | cluster访问最大重定向次数，**建议值：3**。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| password     | 连接Redis实例的密码。如果实例已开启免密访问，无需输入实例的访问密码**。** 忘记密码或需要重置密码请参见[重置缓存实例密码](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-0312039.html#dcs-ug-0312039__section7753521111)。 - 如果使用创建Redis实例时自定义的密码（即实例默认账号的密码），请将其修改为实际密码。  - 如果使用实例创建的ACL账号连接，实例的密码需要配置为"账号名称:账号密码"，即**{username:password}** 。创建或查看ACL账号，请参见[配置Redis ACL访问账号](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-221220.html)。   |
   
表4LettuceClientConfiguration参数 
| 参数            | 类型            | 默认值    | 说明                                        |
|:---|:---|:---|:---|
| timeout       | Duration      | 60s    | 命令超时时间配置，**建议值：2s**。                      |
| clientOptions | ClientOptions | -      | 配置项。                                      |
| readFrom      | readFrom      | MASTER | 读取模式，建议值：MASTER，其余配置在发生故障切换场景下，均存在访问失败风险。 |
   
表5LettucePoolingClientConfiguration参数 
| 参数            | 类型                      | 默认值    | 说明                                        |
|:---|:---|:---|:---|
| timeout       | Duration                | 60s    | 命令超时时间配置，**建议值：2s**。                      |
| clientOptions | ClientOptions           | -      | 配置项。                                      |
| poolConfig    | GenericObjectPoolConfig | -      | 连接池配置。                                    |
| readFrom      | readFrom                | MASTER | 读取模式，建议值：MASTER，其余配置在发生故障切换场景下，均存在访问失败风险。 |
   
表6ClientOptions参数 
| 参数                               | 类型                   | 默认值                          | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|:---|:---|:---|:---|
| autoReconnect                    | boolean              | true                         | 连接断开后，是否自动发起重连，**建议值：true**。                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| pingBeforeActivateConnection     | boolean              | true                         | 连接创建后，是否通过ping/pong校验连接可用性，**建议值：true**。                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| cancelCommandsOnReconnectFailure | boolean              | true                         | 连接重连失败时，是否取消队列中的命令，**建议值：false**。                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| disconnectedBehavior             | DisconnectedBehavior | DisconnectedBehavior.DEFAULT | 连接断开时的行为，**建议值：ACCEPT_COMMANDS**。 - DEFAULT：当autoReconnect为true时，允许命令进入队列等待，当autoReconnect为false时，禁止命令进入队列等待。  - ACCEPT_COMMANDS：允许命令进入队列等待。  - REJECT_COMMANDS：禁止命令进入队列等待。   |
| socketOptions                    | SocketOptions        | -                            | 网络配置项。                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
   
表7SocketOptions参数 
| 参数             | 默认值 | 说明                   |
|:---|:---|:---|
| connectTimeout | 10s | 连接超时时间配置，**建议值：2s**。 |
   
表8GenericObjectPoolConfig参数 
| 参数                             | 默认值     | 说明                                                                                    |
|:---|:---|:---|
| minIdle                        | -       | 连接池的最小连接数。                                                                            |
| maxIdle                        | -       | 连接池的最大空闲连接数。                                                                          |
| maxTotal                       | -       | 连接池的最大连接数。                                                                            |
| blockWhenExhausted             | true    | 连接池耗尽后是否需要等待，默认true表示等待。当值为true时，设置maxWaitMillis才会生效。                                 |
| maxWaitMillis                  | -1      | 连接池耗尽后获取连接的最大等待时间，默认-1表示一直等待。                                                         |
| testOnCreate                   | false   | 创建连接时校验有效性(ping)，默认false。                                                             |
| testOnBorrow                   | false   | 获取连接时校验有效性(ping)，默认false，业务量大时建议设置为false减少开销。                                         |
| testOnReturn                   | false   | 归还连接时校验有效性(ping)，默认false，业务量大时建议设置为false减少开销。                                         |
| testWhileIdle                  | false   | 是否开启空闲连接检测，如为false，则不剔除空闲连接，**建议值：true**。                                             |
| softMinEvictableIdleTimeMillis | -1      | 连接空闲多久后逐出，（空闲时间\>该值 \&\& 空闲连接数\>最小空闲连接数）时直接逐出，**建议值：** **1800000** **，**单位：毫秒。        |
| minEvictableIdleTimeMillis     | 1800000 | 根据minEvictableIdleTimeMillis判断逐出，**建议值：-1**，关闭该策略，改用softMinEvictableIdleTimeMillis策略。 |
| timeBetweenEvictionRunsMillis  | -1      | 空闲连接逐出的检测周期，**建议值：60000**，单位：毫秒。                                                      |
   
#### DCS实例配置建议
- 连接池化 因lettuce底层采用基于netty的NIO模式，和redis server进行通信，不同于jedis的BIO模式。底层采用长连接 + 队列的组合模式，借助TCP顺序发、顺序收的特性，来实现同时处理多请求发送和多响应接收，单条连接可支撑的QPS在3K\~5K不等，线上系统建议不要超过3K。lettuce本身不支持池化，且在springboot中默认不开启池化，如需开启池化，需通过手动引入commons-pool2组件，并关闭LettuceConnectionFactory.shareNativeConnection（共享连接）来实现池化。
  因每条lettuce连接默认需要配置两个线程池-I/O thread pools、computation thread pool，用于支撑IO事件读取和异步event处理，如配置成连接池形式使用，每个连接都将会创建两个线程池，对内存资源的占用偏高。**鉴于lettuce的底层模型实现，及单连接突出的处理能力，不建议通过池化的方式使用lettuce。**
  
- 拓扑刷新 在连接cluster类型实例中，lettuce会在初始化时，向配置的节点列表随机发送cluster nodes来获取集群slot的分布信息。如后续cluster扩/缩容、主备切换等，会导致集群拓扑结构发生变化，**lettuce默认是不感知的，需手动开启主动感知拓扑结构变化** **，**如下：
  - **基于application.properties配置**
    ```
    # 开启自适应拓扑刷新
    spring.redis.lettuce.cluster.refresh.adaptive=true
    # 开启每10s定时刷新拓扑结构
    spring.redis.lettuce.cluster.refresh.period=10S
    ```
    
  
  - **基于API配置**
    ```
    ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
        .enableAllAdaptiveRefreshTriggers()
        .enablePeriodicRefresh(Duration.ofMillis(redisClusterTopologyRefreshPeriodMillis))
        .build();
    ClusterClientOptions clientOptions = ClusterClientOptions.builder()
            ...
            ...
            .topologyRefreshOptions(topologyRefreshOptions)
            .build();
    ```
    
   
- 爆炸半径 因lettuce底层采用的是单长连接 + 请求队列的组合模式，一旦遇到网络抖动/闪断，或连接失活，将影响所有请求，尤其是在连接失活场景中，将尝试tcp重传，直至重传超时关闭连接，待连接重建后才能恢复。在重传期间请求队列会不断堆积请求，上层业务非常容易出现批量超时，甚至在部分操作系统内核中的重传超时配置过长，致使业务系统长时间处于不可用状态。因此，**不推荐使用lettuce组件，建议用jedis组件替换**。
  
 
#### 相关文档
如果Redis连接过程出现问题，请参见[Redis连接失败问题排查和解决](https://support.huaweicloud.com/trouble-dcs/dcs-trouble-0715001.html)。
