本作品采用知识共享署名-非商业性使用 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. 5ucms的评论列表该怎么写

    查看所有评论 <a href="{sys:plusurl}comment/?id={field:id}">查看所有评论</a> <linkhref=& ...

  2. Java基础系列(33)- 计算器

    package method; import java.util.Scanner; public class Demo09 { static double result; static String ...

  3. Shell系列(15)- 数值运算方法

    数值运算-方法1 declare -i [root@localhost ~]# aa=11 [root@localhost ~]# aa=22   #给变量aa和bb赋值 [root@localhos ...

  4. P1013 [NOIP1998 提高组] 进制位

    解析 看到这道题时,有没有想到搜索?然后就是一通码......然后过了. 但是,真的要用搜索吗? 我们可以观察一下.对于n进制中的数ii,如果ii加上某一个数jj会变成两位数,那么可以得到如下不等式: ...

  5. 启动docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock

    启动docker提示: docker: Got permission denied while trying to connect to the Docker daemon socket at uni ...

  6. Jenkins持续交付实战演练

    jenkins web hook机制 运行jenkins任务触发方式: 主动运行 定时构建 就算代码库没有更新,也会构建. 通过代码库主动触发Jenkins的构建任务 jenkins向外暴露一个触发器 ...

  7. Modern PHP 使用生成器yield 处理csv文件 Generator

    * 使用生成器处理csv文件 <?php function getRows($file) { $handle = fopen($file, 'rb'); if ($handle === fals ...

  8. python继承细节

    不要子类化内置类型 内置类型(由C语言编写)不会调用用户定义的类覆盖的特殊方法. 例如,子类化dict作为测验: class DoppeDict(dict): def __setitem__(self ...

  9. file_get_contents('php://input') 数据如何转换成数组

    前台表单页:demo01.html 后台:demo01.php 输出结果: 备注:若前台通过Ajax的post提交过来的是json数据,需要对json数据进行解析:$data = json_decod ...

  10. P7717-「EZEC-10」序列【Trie】

    正题 题目链接:https://www.luogu.com.cn/problem/P7717 题目大意 求有多少个长度为\(n\)的序列\(a\)满足,都在\([0,k]\)的范围内且满足\(m\)个 ...