
# 配置Redis客户端重试机制
#### 重试的重要性
无论是客户端还是服务端，都有可能受到基础设施或者运行环境的影响，遇到暂时性的故障（例如瞬时的网络抖动/磁盘抖动，服务暂时不可用或者调用超时等），从而导致Redis操作失败。通过设计完备的自动重试机制可以大幅降低此类故障的影响，保障操作最终能成功执行。
#### 引发Redis操作失败的场景
| **场景**        | **说明**                                                                                                                                                      |
|:---|:---|
| 故障触发了主备倒换     | 因Redis底层硬件或其他原因导致主节点故障后，会触发主备倒换，保障实例仍可用，主备倒换会产生约15到30秒的实例连接中断。                                                                                              |
| 变更实例规格过程中短暂只读 | 变更规格过程中可能会出现秒级的实例连接中断和分钟级的只读。 更多变更规格可能产生的影响，请参考[变更规格](https://support.huaweicloud.com/usermanual-dcs/dcs-ug-0713006.html)。 |
| 慢查询引起了请求堵塞    | 执行时间复杂度为O(N)的操作，引发慢查询和请求的堵塞，此时，客户端发起的其他请求可能出现暂时性失败。                                                                                                         |
| 复杂的网络环境       | 由于客户端与Redis服务器之间复杂网络环境引起，可能出现偶发的网络抖动、数据重传等问题，此时，客户端发起的请求可能会出现暂时性失败。                                                                                         |
| 复杂的硬件问题       | 由于客户端所在的硬件偶发性故障引起，例如虚拟机HA，磁盘时延抖动等场景，此时，客户端发起的请求可能会出现暂时性失败。                                                                                                  |
   
#### 推荐的重试准则
| **重试准则**      | **说明**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|:---|:---|
| 仅重试幂等的操作      | 由于超时可能发生在下述任一阶段： - 该命令由客户端发送成功，但尚未到达Redis。  - 命令到达Redis，但执行超时。  - 命令在Redis中执行结束，但结果返回给客户端时发生超时。   执行重试可能导致某个操作在Redis中被重复执行，因此不是所有操作均适合设计重试机制。通常推荐仅重试幂等的操作，例如SET操作，即多次执行SET a b命令，那么a的值只可能是b或执行失败；如果执行LPUSH mylist a则不是幂等的操作，可能导致mylist中包含多个a元素。 |
| 适当的重试次数与间隔    | 根据业务需求和实际场景调整适当的重试次数与间隔，否则可能引发下述问题： - 如果重试次数不足或间隔太长，应用程序可能无法完成操作而导致失败。  - 如果重试次数过大或间隔过短，应用程序可能会占用过多的系统资源，且可能因请求过多而堵塞在服务器上无法恢复。   常见的重试间隔方式包括立即重试、固定时间重试、指数增加时间重试、随机时间重试等。                                                                                                                                             |
| 避免重试嵌套        | 重试嵌套可能导致重试时间被指数级放大。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| 记录重试异常并打印失败报告 | 在重试过程中，建议在WARN级别上打印重试错误日志，同时，仅在重试失败时打印异常信息。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
   
#### Jedis客户端重试配置
- 原生JedisPool（操作单机，主备，Proxy集群）模式下，Jedis不提供重试功能，因此需要自己封装重试。可以参考[JedisClusterCommand](https://github.com/redis/jedis/blob/v3.7.0/src/main/java/redis/clients/jedis/JedisClusterCommand.java)的实现方法，自行实现JedisPool的重试方法。

- 在JedisCluster模式下，Jedis提供了重试功能，可以配置maxAttempts参数来定义失败时的重试次数（默认值为5）。JedisCluster的所有操作都默认调用了重试的方法。 示例代码：
  ```
  @Bean
  JedisCluster jedisCluster() {
      Set<HostAndPort> hostAndPortsSet = new HashSet<>();
      hostAndPortsSet.add(new HostAndPort(“{dcs_instance_address}”, 6379));
      JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
      jedisPoolConfig.setMaxIdle(100);
      jedisPoolConfig.setMinIdle(1);
      jedisPoolConfig.setMaxTotal(1000);
      jedisPoolConfig.setMaxWaitMilis(2000);
      jedisPoolConfig.setMaxAttempts(5);
      return new JedisCluster(hostAndPortsSet, jedisPoolConfig);
  }
  ```
  
表1Jedis连接池参数配置建议 
| 参数                            | **配置介绍**                                        | **配置建议**                                                                                                                                                                                                                                                                                                                                                                                                                                     |
|:---|:---|:---|
| maxTotal                      | 最大连接，单位：个                                       | 根据Web容器的Http线程数来进行配置，估算单个Http请求中可能会并行进行的Redis调用次数，例如：Tomcat中的Connector内的maxConnections配置为150，每个Http请求可能会并行执行2个Redis请求，在此之上进行部分预留，则建议配置至少为：150 x 2 + 100= 400 **限制条件：**单个Redis实例的最大连接数。maxTotal和客户端节点数（CCE容器或业务VM数量）数值的乘积要小于单个Redis实例的最大连接数。 例如：Redis主备实例配置maxClients为10000，单个客户端maxTotal配置为500，则最大客户端节点数量为20个。 |
| maxIdle                       | 最大空闲连接，单位：个                                     | 配置与maxTotal一致。                                                                                                                                                                                                                                                                                                                                                                                                                               |
| minIdle                       | 最小空闲连接，单位：个                                     | 一般来说建议配置为maxTotal的X分之一，例如此处常规配置建议为：100。 对于性能敏感的场景，为了防止经常连接数量抖动造成影响，可以配置与maxIdle一致，例如：400。                                                                                                                                                                                                                                                                                     |
| maxWaitMillis                 | 最大获取连接等待时间，单位：毫秒                                | 获取连接时最大的连接池等待时间，根据单次业务最长容忍的失败时间减去执行命令的超时时间得到建议值。例如：Http最长容忍的失败时间为15s，Redis请求的timeout设置为10s，则此处可以配置为5s。                                                                                                                                                                                                                                                                                                                                       |
| timeout                       | 命令执行超时时间，单位：毫秒                                  | 单次执行Redis命令最大可容忍的超时时间，根据业务程序的逻辑进行选择，出于对网络容错等考虑建议配置为不小于210ms。特殊的探测逻辑或者环境异常检测等，可以适当调整达到秒级。                                                                                                                                                                                                                                                                                                                                                     |
| minEvictableIdleTimeMillis    | 空闲连接逐出时间，大于该值的空闲连接一直未被使用则会被释放，单位：毫秒             | 如果希望系统不会经常对连接进行断链重建，此处可以配置一个较大值（xx分钟），或者此处配置为-1并且搭配空闲连接检测进行定期检测。                                                                                                                                                                                                                                                                                                                                                                             |
| timeBetweenEvictionRunsMillis | 空闲连接探测时间间隔，单位：毫秒                                | 根据系统的空闲连接数量进行估算，例如系统的空闲连接探测时间配置为30s，则代表每隔30s会对连接进行探测，如果30s内发生异常的连接，经过探测后会进行连接排除。根据连接数的多少进行配置，如果连接数太大，配置时间太短，会造成请求资源浪费。对于几百级别的连接，常规来说建议配置为30s，可以根据系统需要进行动态调整。                                                                                                                                                                                                                                                                                 |
| testOnBorrow                  | 向资源池借用连接时是否做连接有效性检测（ping），检测到的无效连接将会被移除。        | 对于业务连接极端敏感的，并且性能可以接受的情况下，可以配置为True，一般来说建议配置为False，不启用连接空闲检测。                                                                                                                                                                                                                                                                                                                                                                                 |
| testWhileIdle                 | 是否在空闲资源监测时通过ping命令监测连接有效性，无效连接将被销毁。             | True                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| testOnReturn                  | 向资源池归还连接时是否做连接有效性检测（ping），检测到无效连接将会被移除。         | False                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| maxAttempts                   | 在JedisCluster模式下，您可以配置maxAttempts参数来定义失败时的重试次数。 | 建议配置3-5之间，默认配置为5。 根据业务接口最大超时时间和单次请求的timeout综合配置，最大配置不建议超过10，否则会造成单次请求处理时间过长，接口请求阻塞。                                                                                                                                                                                                                                                                                            |
   
