Jedis第一讲-集群断连引发的超时问题
Jedis是开源的面向java的Redis数据库客户端工具包之一,目前github版本是3.1.0 ()。对于Spring Boot项目来说,starter-data-redis:1.5.12.release 版本所依赖的redis Client包是2.9.0版本。从maven的依赖引用上看,官方引用最多的也是此版本。所以本文在源码讲解上也是依据2.9.0版本进行说明的。
众所周知,所有项目对于
redis数据库的依赖无非出于以下两种原因:一方面,依赖其内存数据库特性,将
redis作为分布式缓存使用,同时以降低对关系型数据库的
OPS操作;另一方面,依赖其单线程执行的特性,以实现分布式锁的功能。大多数业务场景,我们还是将
redis作为了文本型的缓存服务器在使用,以提高请求的相应效率,降低平响时间,增加服务的吞吐。但是,今天我们聊一下在集群环境下,集群整体不可用时,产生的
redis超时过长,最终拖慢请求的场景。
5主5从 Redis集群
在大多数应用中,我们都会采取
redis集群的方式,以保证服务的高可用和高吞吐,有时也会多个应用服务连接一个
redis集群。已达到资源的高利用率。在正常情况下集群方式提高了
redis整体的吞吐能力,同时通过主
-从方式可以实现故障偏移(主节点挂掉,由从节点升级为主节点)。在集群模式下,
redis
16384个
slot会被平摊到
5个主节点上,此时也起到了负载均衡的效果,将流量进行了分流。(这也添加了另外一个名词:热
key问题)。已上为集群模式相比单例模式带来的优势。然而,今天我们要聊的就是这种集群模式下,由于集群整体不可用时,
Jedis一次请求操作超时远远超出设置的
timeout 时间问题。
Redis集群不可用
正常情况下,如果集群中某一台机器挂掉,redis集群会执行故障偏移策略,将其对应的从节点升级为主节点,超时时间将为一个timeout,。然而,当整个集群与应用服务之间出现了网络断链时,超时时间=timeout
* 集群节点数(主+从)。这是为什么呢?是因为在集群模式下,如果某个节点挂掉,集群会执行故障偏移,偏移过程中涉及到新的主节点选举以及新的从节点加入。Jedis为了能够获取到新的集群信息,会从原始的集群信息列表中某一个节点开始遍历,如果能够连接该节点,就从该节点获取整个集群的配置信息和slot分布。如果当前节点已经断连,Jedis会获取另外一个节点信息获取集群配置,直到遍历到最后一个节点为止。所以在整个过程中如果整个集群断连,那么一次请求timeout时间 >= timeout * 节点数。
Jedis Client(
2.9.0)版本关于上述业务处理的代码主要在
JedisClusterCommand类的
runWithRetries方法中。
该方法有四个参数:
Key : 要进行操作的key
Attempts : 请求重试次数
TryTrandomNode : 是否尝试随机选择一个节点进行
Asking : 是否处理asking 链接(可以了解一下asking 和moved 区别)
具体方法体如下:
如果重试次数
attempts <= 0则直接抛异常,该操作是用来做递归程序终止的判定条件。如果不满足条件
,则判断是否支持
asking方式获取链接。否则执行
else操作:
否则判断是否尝试随机获取一个节点获取链接
tryRandomNode,
否则通过当前
key获取
slot,再通过
slot获取
slot所在节点的
connection。
如果在获取链接时或者执行execute操作时出现异常,则执行异常处理机制,此处的异常处理分为:
1、JedisNoReachableClusterNodeException 说明Jedis在初始化时没有获取到任何集群信息,即在服务启动时集群就是不可用的,所以这种状态下,必须检查redis集群的可用性,并重启应用服务。
2、JedisConnectionException 说明
Jedis在获取链接时出现异常,主要原因是因为
Jedis在获取连接时默认会
ping一下连接以保证目标节点是可用的,如果
ping过程失败,则抛出该异常。
这个异常是今天我们要研究的重点内容,主要说明为什么集群不可用时,提效缓存,引发了超时问题?图中展示代码是在JedisConnectionException的时候,Jedis Client会执行如下步骤:
1、释放connection链接
2、判断设置的重试次数是否
<=1,如果为
false,则调用
runWithRetries方法递归调用进行重试访问,如果为
true,则会调用
connectionHandler对象的
renewSlotCache()方法:
该类是一个
abstract类,内部属性
JedisClusterInfoCache类对象。
renewSlotCache()方法内部调用
cache对象的
renewClusterSlots(null)方法:
该方法内部才是真正超时根源。从方法内容可以看出存在一个
for循环操作,循环内部是通过遍历缓存中
redis节点列表(包含主节点
+从节点),通过与节点通信的方式获取最新的集群信息,如果访问某个节点出现异常,则访问下一个节点,直到最后一个节点。要知道在一个集群整体都挂断时,此处的
for循环一定会遍历所有节点,最终在都访问不同的状态下返回。那为什么会导致超时呢?大家想一想,
Jedis的配置,一般状态下我们对于线上应用配置的
redis maxWaitTimes参数
一般都是
50ms ,如果整个集群不可用时,如果是
5主
+5从的集群,那么
50*12 = 600ms (为什么是
12呢,
12 = 10(节点数
) + 2 (
attempts)
),所以一个请求最低在
redis这一层就报销了
600ms。所以
redis集群整体的不可用对服务带来的影响是非常大的。希望在以后的开发中牢记这点。
3、JedisRedirectionException 该异常主要原因有两个。一种是由于
Moved操作导致的(
slot不在被访问节点,在另外一个节点时,当前节点返回
Moved错误,并附带上目标节点
IP+端口),另外一种是
asking导致的(在节点进行迁移过程中,如果被访问的
slot属于正在迁移节点,并且此时,访问的
key不在当前节点,会返回
asking错误,让
client访问目标节点获取数据)。这两种情况虽然结果一直,即都需要跳转到另外一个节点获取最终结果,但是原因是截然不同的。如果客户端使用
Jedis时,并且在集群环境下,一般不会出现
Moved错误,因为
Jedis在
client端 就已经通过
CRC16(key)计算好了
slot以及
slot具体分布在哪个节点上,所以一般不会出现
Moved问题。当然只有一种情况就是当被访问的
key所在主节点挂掉时,集群进行故障偏移,
Jedis需要通过其他节点重新获取最新集群信息时,有可能会出现
Moved问题。再有
2就是通过命令行链接集群时,
redis-cli -c ip:port 时,如果访问的
key不在该节点上时,
server会返回
Moved错误。
所以从源码中大家会发现,集群模式下,如果server端给Jedis返回JedisMovedDataException错误时,Jedis什么都没有执行(do nothing),这就很符合上面提到的结论。
阅读(285969) | 评论(0) | 转发(0) |