关于Redis集群方面的7个技术点,80%的人都没有深入了解过

255 阅读8分钟

本文主要讲解Redis在集群方面的一些知识点:

Redis 集群方案应该怎么做?都有哪些方案?

twemproxy

大概概念是,它类似于一个代理方式,使用方法和普通Redis 无任何区别,设置好它下属的多个 Redis 实例后,使用时在本需要连接 Redis 的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 Redis,将结果再返回 twemproxy。使用方式简便(相对 Redis 只需修改连接端口),对旧项目扩展的首选。

整体的架构如下图所示,连接都会被交给代理方去处理,

image.png

这套架构方案的特点是:

  • 轻量级、快速
  • 保持长连接
  • 减少了直接与缓存服务器连接的连接数量
  • 使用 pipelining 处理请求和响应
  • 支持代理到多台服务器上
  • 同时支持多个服务器池
  • 自动分片数据到多个服务器上

但是它也存在一定的缺点,主要的缺陷如下: 当新增了redis实例后,缓存的命中会因为hash一致性算法落到别的机器上,从而命中失败。

codis

这套方案的设计思路和上边我们说到的twemproxy相同,只不过它可以支持当redis节点发生变迁的时候,旧数据可以被迁移到新数据节点上。

Redis Cluster

Redis Cluster是基于hash槽的概念去分配数据存储的,可以支持每个hash槽节点(slot)的数据搭建从节点部署。 Redis Cluster的底层支持按照键空间划分为16384块,每块都被我们称之为槽节点,不同区间段的槽节点交给不同的node负责,node节点可以理解为是具体的Redis机器。

image.png

在Redis的集群模式中,每个节点负责一定数量的槽(slot),并且维护一个Cluster Map来管理槽的分配情况。当客户端向集群中的某个节点发起请求时,该节点会根据请求中的key计算出对应的槽位,然后检查自己是否负责该槽位。

如果该节点不负责该槽位,就会返回一个MOVED错误,告诉客户端该请求应该转发到哪个节点处理。MOVED错误会包含目标节点的地址和端口信息,客户端将该错误信息响应回去。

最终,客户端接收到MOVED错误之后,根据错误信息重新定位到正确的节点,并重新向正确节点发起请求。因此,当节点不负责当前请求的槽位时,它可以通过MOVED错误,帮助客户端定位正确的节点,从而实现请求的转发和处理。

需要注意的是,MOVED错误是Redis集群模式中非常重要的机制之一,实现MOVED错误需要Redis集群中所有节点的协同配合,并且客户端需要能够正确处理MOVED错误。如果节点之间的通信不正常,MOVED错误会带来槽重新分配的问题,会影响Redis集群的正常运行。

客户端分片

在业务代码层实现,起几个毫无关联的 Redis 实例,在代码层,对 key 进行 hash 计算, 然后去对应的 Redis 实例操作数据。 这种方式对 hash 层代码要求比较高,考虑部分包括, 节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。

Redis 分区有什么缺点?

涉及多个 key 的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存 储到不同的 Redis 实例(实际上这种情况也有办法,但是不能直接使用交集指令)。 同时操作多个 key,则不能使用 Redis 事务。

分区使用的粒度是 key,不能使用一个非常长的排序 key 存储一个数据集

(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set)。

当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的 Redis 实例和主 机同时收集 RDB / AOF 文件。

分区时动态扩容或缩容可能非常复杂。Redis 集群在运行时增加或者删除 Redis 节点,能 做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持 这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。

Redis Cluster的节点数据共享是怎么做的

Gossip通信中的四种消息类型

Redis Cluster使用Gossip协议维护节点的元数据信息,这种协议是P2P模式的,主要指就是信息交换。节点间不停地去交换彼此的元数据信息,那么总会在一段时间后,大家都知道彼此是谁,负责哪些数据,是否正常工作等等。节点间信息交换是依赖于彼此发出的Gossip消息的。常用的一般是以下四种消息:

  • meet消息 会通知接收该消息的节点,发送节点要加入当前集群,接收者进行响应。

  • ping消息 是集群中的节点定期向集群中其他节点(部分或全部)发送的连接检测以及信息交换请求,消息包含发送节点信息以及发送节点知道的其他节点信息。

  • pong消息是在节点接收到meet、ping消息后回复给发送节点的响应消息,告诉发送方本次通信正常,消息包含当前节点状态。

  • fail消息 是在节点认为集群内另外某一节点下线后向集群内所有节点广播的消息。

image.png

通讯过程

在集群启动的时候,各个节点之间会首先进行握手通信,这个过程的本质就是某个节点给其他节点发送meet消息,当其他的节点收到这个meet消息之后,就会将meet消息包中的数据缓存到本地内存中(其中包括发送方的节点id,槽位,标识等,同时还会包括发送方和其他建立关系的节点的节点id,槽位,标识等数据)。接收方收到握手的meet消息通知后,会返回一个pong请求,告诉对方,同步数据完成。

集群启动后,集群中各节点也会定时往其他部分节点发送ping消息,用来检测目标节点是否正常以及发送自己最新的节点负槽位信息。接收方同样响应pong消息,由发送方更新本地节点信息。

image.png

当在与某一节点通信失败(故障发现策略后面会说)时,则会主动向集群内节点广播fail消息。

考虑到频繁地交换信息会加重带宽(集群节点越多越明显)和计算的负担,Redis Cluster内部的定时任务每秒执行10次,每次遍历本地节点列表,对最近一次接收到pong消息时间大于cluster_node_timeout/2的节点立马发送ping消息,此外每秒随机找5个节点,选里面最久没有通信的节点发送ping消息。同时 ping 消息的消息投携带自身节点信息,消息体只会携带1/10的其他节点信息,避免消息过大导致通信成本过高。

什么场景下Redis Cluster会失效?

有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。这种情况下,就会导致有1/3的数据获取不到,造成整个集群可用性大大下降。

JedisCluster操作集群的过程是怎样的?

首先JedisCluster初始化时,会选择一个正在运行的node, 初始化slot和node的映射关系,使用cluster slots命令完成:

image.png

Jedis解析Cluster slots结果,并缓存到本地,并为每个节点创建唯一的JedisPool连接池。映射关系在JedisClusterInfoCache类中。

image.png

整个流程为:

1、根据key计算出slot, 并根据slot找到node建立连接,执行命令

2、如果连接出现错误,则使用随机连接重新执行命令,每次命令重试对redirections参数减1

3、捕获到MOVED重定向错误,使用cluster slots命令更新slots缓存

4、重复执行前3步,直到命令执行成功,或者当redirections<=0 时抛出异常。

Redis Cluster 新节点上线怎么做?

Redis Cluster 加入新节点时,客户端需要执行 CLUSTER MEET 命令,如下图所示。

节点一在执行 CLUSTER MEET 命令时会首先为新节点创建一个 clusterNode 数据,并将其添加到自己维护的 clusterState 的 nodes 字典中。有关 clusterState 和 clusterNode 关系,我们在最后一节会有详尽的示意图和源码来讲解。

然后节点一会根据据 CLUSTER MEET 命令中的 IP 地址和端口号,向新节点发送一条 MEET 消息。新节点接收到节点一发送的MEET消息后,新节点也会为节点一创建一个 clusterNode 结构,并将该结构添加到自己维护的 clusterState 的 nodes 字典中。

接着,新节点向节点一返回一条PONG消息。节点一接收到节点B返回的PONG消息后,得知新节点已经成功的接收了自己发送的MEET消息。

最后,节点一还会向新节点发送一条 PING 消息。新节点接收到该条 PING 消息后,可以知道节点A已经成功的接收到了自己返回的P ONG消息,从而完成了新节点接入的握手操作。

MEET 操作成功之后,节点一会通过前边我们提到的定时 PING 机制将新节点的信息发送给集群中的其他节点,让其他节点也与新节点进行握手,最终,经过一段时间后,新节点会被集群中的所有节点认识。

节点疑似下线和真正下线如何判断?

Redis Cluster 中的节点会定期检查已经发送 PING 消息的接收方节点是否在规定时间 ( cluster-node-timeout ) 内返回了 PONG 消息,如果没有则会将其标记为疑似下线状态,也就是 PFAIL 状态,如下图所示。

然后,节点一会通过 PING 消息,将节点二处于疑似下线状态的信息传递给其他节点,例如节点三。节点三接收到节点一的 PING 消息得知节点二进入 PFAIL 状态后,会在自己维护的 clusterState 的 nodes 字典中找到节点二所对应的 clusterNode 结构,并将主节点一的下线报告添加到 clusterNode 结构的 fail_reports 链表中。

随着时间的推移,如果节点十 (举个例子) 也因为 PONG 超时而认为节点二疑似下线了,并且发现自己维护的节点二的 clusterNode 的 fail_reports 中有半数以上的主节点数量的未过时的将节点二标记为 PFAIL 状态报告日志,那么节点十将会把节点二将被标记为已下线 FAIL 状态,并且节点十会立刻向集群其他节点广播主节点二已经下线的 FAIL 消息,所有收到 FAIL 消息的节点都会立即将节点二状态标记为已下线。如下图所示。

需要注意的是,报告疑似下线记录是有时效性的,如果超过 cluster-node-timeout *2 的时间,这个报告就会被忽略掉,让节点二又恢复成正常状态。

本文参考内容:

blog.csdn.net/shijinghan1… cloud.tencent.com/developer/a… juejin.cn/post/684490…