本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

在互联网服务中,特别是在云环境下,网络及硬件环境复杂,所有应用程序都可能遇到暂时性故障。暂时性故障包括瞬时的网络抖动,服务暂时不可用,服务繁忙导致超时等,这些故障通常可以自我修复,在间隔一定时间后重试,则很大概率可以成功。

为什么会出现暂时性故障

云 Redis 集群的高可用

Redis 集群或者云环境中的很多产品,都是由大量机器组成的集群提供服务,机器可能发生各种各样的硬件故障,比如 CPU,内存,磁盘 都可能发生故障。当故障发生时,Redis 服务会自动做 HA (High Available),切换备用节点上来,客户端则可能遇到连接闪断,节点只读 (主动 HA 流程需要对主禁止写入,从而让数据完整同步到备)等暂时性故障。

客户已知的慢查询瞬间打满Redis

如果 Redis 在定期执行一些已知的慢查询,比如(keys, hgetall, smembers)等复杂度为 O(N) 的操作,则可能导致其余 O(1) 复杂度的 API 出现暂时性失败。

复杂的网络环境

云上网络环境复杂,客户端和服务器之间的网络状况会不时改变,偶现网络抖动,数据包重传等问题。

重试的准则

适当的重试次数与间隔

必须优化重试次数与间隔。如果重试次数不足或者间隔太长,应用程序无法完成操作,并且可能失败。如果重试次数太多或者间隔太短,则可能会增大程序对系统资源的占用,且有可能造成服务端堆积太多请求无法恢复。确定适当的重试间隔是最困难的部分,常见的重试间隔如指数退让、增量间隔、固定间隔[1]等。

避免重试嵌套

大多数情况下,应该避免重试嵌套,嵌套容易造成反复重试且无法停止。

记录重试过程异常,但只有最终失败被报告

重试过程中,建议按照 WARN 级别打印重试的错误日志,但是只有最终重试失败时候才抛出 Exception,重试过程中如果能完成请求的正确访问,则不会打扰到用户。

超时之后不一定就可以重试

超时是一种未决现象,配置超时时间 2s,那么客户端通常的行为是当命令发送之后开始计时,如果在 2s 内没有成功收到回复,则客户端抛出 Timeout Exception。但此时服务端有没有执行过命令呢?是未决的,因为超时可能发生在任何阶段:

  • 命令被客户端发出,但是还没有到达 Redis
  • 命令到达 Redis,但是执行时候超时
  • 命令执行结束,但是结果返回客户端时候超时

因此,对于有的命令,超时之后就不一定可以重试,是否可以重试的准则是命令执行是否幂等。

幂等操作才能重试

需要确定重试的操作是否为幂等的,例如:

  • set a b,就是幂等的,多次执行最终 a 的值只可能为 b 或者失败。
  • lpush list a,就不是幂等的,如果多次 lpush list a,则 list 中可能包含多个 a 元素。

Redis 常见客户端如何做重试

Redis 客户端开源生态繁荣,几乎各个编程语言都有对应的 Redis 客户端,Redis 官网(https://redis.io/clients)列出了很多 Redis 客户端,下面我们就常见的几个客户端讨论如何做重试。

Jedis

如果是 JedisCluster 模式,可以通过配置 maxAttempts 参数来定义失败情况下的重试次数,默认值为 5。此参数实际上是为了处理 Redis Cluster 集群路由变更时,客户端更新路由表用的,但是也可以用来作为 API 访问失败重试的控制参数。
如果是普通 JedisPool 模式,Jedis 本身不提供重试功能,因此我们通过 alibabacloud-tairjedis-sdk 封装了一个 Jedis 的重试类,可以方便你完成 Jedis 的重试操作。alibabacloud-tairjedis-sdk 是基于 Jedis 封装的,可以访问 Redis 社区版,并支持操作Redis企业版 的客户端,支持多种 Module 的操作命令。

1,添加依赖

<dependency>
<groupId>com.aliyun.tair</groupId>
<artifactId>alibabacloud-tairjedis-sdk</artifactId>
<version>latest</version>
</dependency>

2,下面的代码会将 set key value 命令自动重试5次,且总的重试时间不超过10s,每次重试之间等待类指数间隔的时间,如果最终不成功,则抛出异常。

int maxRetries = 5; // 最大重试次数
Duration maxTotalRetriesDuration = Duration.ofSeconds(10); // 最大的重试时间
try {
String ret = new JedisRetryCommand<String>(jedisPool, maxRetries, maxTotalRetriesDuration) {
@Override
public String execute(Jedis connection) {
return connection.set("key", "value");
}
}.runWithRetries();
} catch (JedisException e) {
// 表示尝试 maxRetries 次或者到达了最大查询时间 maxTotalRetriesDuration 仍旧没有访问成功
e.printStackTrace();
}

Lettuce

Lettuce 本身不提供命令超时之后可以重试的参数,但是 Lettuce 有两种命令执行可靠性选项:

  • at-most-once execution:最多执行一次,即此命令执行零次或者一次,如果连接断开重连,则可能会丢失。
  • at-least-once execution(默认):最少执行一次,即使链接重连,也会将命令再次执行。

上面的两种选项都是通过 autoReconnect 这个客户端选项控制的:

clientOptions.isAutoReconnect() ? Reliability.AT_LEAST_ONCE : Reliability.AT_MOST_ONCE;

autoReconnect 默认是 true,即默认为 at-least-once,这个选项会带来的一个问题是,如果 Redis HA 之后,客户端可能堆积满的重试命令发过来会造成 Redis CPU 尖峰,社区也有人反馈这个问题,作者也在考虑引入一种新的过滤机制,可以控制重连之后重试逻辑。

Redisson

Redisson 提供了重试次数 retryAttempts (默认为3次) 和重试间隔 retryInterval (默认为1500ms) 两个参数来控制重试逻辑,如果想更改,可以参考下面的代码:

Config config = new Config();
config.useSingleServer()
.setTimeout(1000)
.setRetryAttempts(x)
.setRetryInterval(x) //ms
.setAddress("redis://127.0.0.1:6379");
RedissonClient connect = Redisson.create(config);

StackExchange.Redis

StackExchang.Redis 目前客户端本身只支持 connect 时候的重试,可参考

var conn = ConnectionMultiplexer.Connect("redis0:6380,redis1:6380,connectRetry=3");

对于 API 层面的重试,可以通过 Polly 来完成。

总结

本文总结了 Redis 为什么需要做重试,以及重试应当遵循的准则。并举例了常见客户端 Jedis、Lettuce、Redisson、StackExchange.Redis 如何做重试,对于 Jedis,因为其用户使用量最大且没有成熟方案,因此在 alibabacloud-tairjedis-sdk 中提供了重试类让用户集成使用。

参考:
[1] Transient fault handling https://docs.microsoft.com/zh-cn/azure/architecture/best-practices/transient-faults

Redis 客户端重试指南的更多相关文章

  1. 全球领先的redis客户端:SFedis

    零.背景 这个客户端起源于我们一个系统的生产问题. 一.问题的发生 在我们的生产环境上发生了两次redis服务端连接数达到上限(我们配置的单节点连接数上限为8000)导致无法创建连接的情况.由于这个系 ...

  2. RabbitMQ-Java客户端API指南-下

    RabbitMQ-Java客户端API指南-下 使用主机列表 可以将Address数组传递给newConnection().的地址是简单地在一个方便的类com.rabbitmq.client包与主机 ...

  3. Golang 实现 Redis(6): 实现 pipeline 模式的 redis 客户端

    本文是使用 golang 实现 redis 系列的第六篇, 将介绍如何实现一个 Pipeline 模式的 Redis 客户端. 本文的完整代码在Github:Godis/redis/client 通常 ...

  4. redis scan 命令指南

    redis scan 命令指南 1. 模糊查询键值 redis 中模糊查询key有 keys,scan等,一下是一些具体用法. -- 命令用法:keys [pattern] keys name* -- ...

  5. 深入剖析Redis客户端Jedis的特性和原理

    一.开篇 Redis作为目前通用的缓存选型,因其高性能而倍受欢迎.Redis的2.x版本仅支持单机模式,从3.0版本开始引入集群模式. Redis的Java生态的客户端当中包含Jedis.Rediss ...

  6. StackExchange.Redis客户端读写主从配置,以及哨兵配置。

    今天简单分享一下StackExchange.Redis客户端中配置主从分离以及哨兵的配置. 关于哨兵如果有不了解的朋友,可以看我之前的一篇分享,当然主从复制文章也可以找到.http://www.cnb ...

  7. Redis 数据库入门指南

    Redis 是一个开源数据库,它使用内存数据结构存储,可作为数据库.缓存和消息代理使用.Redis 支持丰富的数据结构,有:字符串(Strings).哈希(Hashs).列表(Lists).集合(Se ...

  8. c#实现redis客户端(一)

    最近项目使用中要改造redis客户端,看了下文档,总结分享一下. 阅读目录: 协议规范 基础通信 状态命令 set.get命令 管道.事务 总结 协议规范 redis允许客户端以TCP方式连接,默认6 ...

  9. 使用StackExchange.Redis客户端进行Redis访问出现的Timeout异常排查

    问题产生 这两天业务系统在redis的使用过程中,当并行客户端数量达到200+之后,产生了大量timeout异常,典型的异常信息如下: Timeout performing HVALS Parser2 ...

随机推荐

  1. javascript wchar_t 宽字符 转化为 ascii字符码数组

    String.prototype.charCodeAt String.fromCharCode() String.prototype.toUtfArray = function() { return ...

  2. LR11可打开网页,但录制为空

    环境:WIN7+LR11+360安全浏览器9.0 LR11可打开网页,但录制为空解决方案:(3步) 1. tools-Recording Options -->Network-->Port ...

  3. 实验3:OpenFlow协议分析实践

    作业链接:实验3:OpenFlow协议分析实践 一.实验目的 能够运用 wireshark 对 OpenFlow 协议数据交互过程进行抓包: 能够借助包解析工具,分析与解释 OpenFlow协议的数据 ...

  4. MyBatis 面试复习整理

    MyBatis MyBatis 是一款优秀的ORM(对象关系映射)框架,可以通过对象和数据库之间的映射,将程序中的对象自动存储到数据库中.它内部封装了 JDBC ,使开发者只需要关注 SQL语句本身, ...

  5. 现在有一个长度20的SET,其中每个对象的内容是随机生成的字符串,请写出遍历删除LIST里面字符串含"2"的对象的代码。

    现在有一个长度20的SET,其中每个对象的内容是随机生成的字符串,请写出遍历删除LIST里面字符串含"2"的对象的代码. public class RemoveTwo { //le ...

  6. WIN10下的VMware与Docker冲突的解决方案

    VMARE版本升级到15.5以上 WIN10升级到2004版本以上 Hyper-V为开启状态

  7. java语言程序设计与数据结构(基础篇)第四章答案

    4.1 import java.util.Scanner; public class Welcome { public static void main(String[] args) { Scanne ...

  8. 初识Linux shell

    目录 初识Linux shell Linux 深入探究Linux内核 系统内存管理 交换空间 页面 换出 软件程序管理 Linux中的进程 Linux系统的运行级 硬件设备管理 插入设备驱动代码的方法 ...

  9. FastAPI 学习之路(六)查询参数,字符串的校验

    系列文章: FastAPI 学习之路(一)fastapi--高性能web开发框架 FastAPI 学习之路(二) FastAPI 学习之路(三) FastAPI 学习之路(四) FastAPI 学习之 ...

  10. vue基础-动态样式&表单绑定&vue响应式原理

    动态样式 作用:使用声明式变量来控制class和style的值 语法: :class/:style 注意:尽可能不要把动态class和静态class一起使用,原因动态class起作用的时间会比较晚,需 ...