Redis Cluster 原理初探

阅读数:355 2019 年 9 月 20 日 14:22

Redis Cluster原理初探

虽然到现在仍未发现公司内部有团队在使用 Redis Cluster,但是这丝毫不影响我们去了解它。Redis Cluster 是一个可以在多个 Redis 节点之间进行数据共享的分布式集群。和以往了解的客户端通过一致性哈希解决 redis 多节点负载均衡的方式不同,Redis Cluster 是在服务端,通过节点之间的特殊协议进行通讯,达到服务端对请求进行负载均衡。对于客户端来说,其负载均衡策略是透明的,客户端不需要自己做负载均衡。

集成简介

Redis Cluster 特性之一是引入了槽的概念。一个 redis 集群包含 16384 个哈希槽,集群中的每个 redis 节点,分配到一部分槽。而集群使用公式 CRC16(key) % 16384 来计算每次请求的键 key 属于哪个槽,通过查询集群配置,便可知道 key 对应的槽属于哪个 redis 节点,然后再将请求打到该节点。举个例子,一个集群可以有两个节点,其中:

1. 节点 A 负责处理 0 号至 5000 号哈希槽。
2. 节点 B 负责处理 5001 号至 10000 号哈希槽。
3. 节点 C 负责处理 10001 号至 16383 号哈希槽。

通过上述公式,可对 key X 计算出一个值,该值为 0-16383 中的一个数。假设 key X 通过上述公式计算出来的值为 34,根据上面例子,34 即为槽标识,亦 key X 属于槽 34,而槽 34 分配到了节点 A,也就是说节点 A 负责 key X 的读写。

通过将哈希槽分布到不同节点,我们可以很容易地向集群中添加或者删除节点。比如说:

  1. 如果用户将新节点 D 添加到集群中,那么集群只需要将节点 A 、B、C 中的某些槽移动到节点 D 就可以了。

  2. 与此类似,如果用户要从集群中移除节点 A ,那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C ,然后再移除节点 A 就可以了。

因为槽在节点之间移动不会造成节点阻塞,所以无论是添加新节点还是移除已存在节点,又或者改变某个节点包含的哈希槽数量,都不会造成集群下线,redis 集群能保证槽的平滑移动。

RedisCluster 还有一个特性便是去中心化。客户端可以连接集群中的任意一个节点,集群中的任意一个节点都可对外提供服务。节点之间可共享集群配置(如槽的分配)。或者我们可以理解为,集群中的任意一个节点都是中心节点。假设有两个节点 A 和 B,客户端连接了 A 节点,并发起了一次请求 a,A 节点计算请求 a 的 key 得知该请求应该打到 B 节点上,然后 A 节点对请求 a 返回一个 MOVED B,通知客户端重定向到 B 节点。

集成简介

redis 集群架构图

Redis Cluster原理初探

因为槽在节点之间移动不会造成节点阻塞,所以无论是添加新节点还是移除已存在节点,又或者改变某个节点包含的哈希槽数量,都不会造成集群下线,redis 集群能保证槽的平滑移动。

以上图片,蓝色的为 redis 节点,这里是指 master 节点,一个 master 节点可以配置多个 slave。绿色为客户端,可以理解为我们的应用。

架构细节:

(1) 所有的 redis 节点彼此互联 (PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。

(2) 节点的 fail 是通过集群中超过半数的节点检测失效或者某个节点主从全挂时才生效。

(3) 客户端与 redis 节点直连,不需要中间 proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

(4)redis-cluster 把所有的物理节点映射到 [0-16383]slot 上。

集群容错

为了当部分节点失效时,cluster 仍能保持可用,Redis 集群采用每个节点拥有 1(主服务自身)到 N 个副本的主从模型。类似于 master/slave。但是 redis cluster 却不是强一致性的,因为 cluster 内部 master 和 slave 之间是通过异步复制做数据同步的,复制过程中可能 master 挂了,这就导致部分数据没有完全同步至 slave 上,不过这种可能性还是很小的。

以上是集群选举过程。

选举过程是集群中所有 master 参与,如果半数以上 master 节点与当前 master 节点通信超时,则集群认为当前 master 节点挂掉.

什么时候整个集群不可用?当集群不可用时, 所有对集群的操作做都将失败。以下是会导致集群不可用的其中两种情况:

a: 集群任意 master 挂掉,并且当前 master 没有 slave,集群不可用。
b: 集群超过半数以上 master 挂掉,无论是否有 slave,集群不可用。

集群扩展

什么时候整个集群不可用?当集群不可用时, 所有对集群的操作做都将失败。以下是会导致集群不可用的其中两种情况:

选举过程是集群中所有 master 参与,如果半数以上 master 节点与当前 master 节点通信超时,则集群认为当前 master 节点挂掉。

以往的一致性哈希方案,如果我们移除或者新增节点时,虽然说不会导致全局 key 的 rehash,但是也会影响到部分 key 的失效。Redis Cluster 在可用性和可扩展性上比较重视,如果集群新增一个节点,在给该节点分配槽时,这些槽所属的源节点和该节点会进行一次 key 的迁移,并且迁移过程中不阻塞集群服务。如果移除一个节点,同理,我们需要将待移除的节点的 key 迁移到另一个节点上。

那集群是如何做到 key 迁移不阻塞集群服务的呢?

key 迁移过程中,涉及到 CLUSTER SETSLOT slot8 MIGRATING node 命令和 CLUSTER SETSLOT slot8 IMPORTING node 命令,前者用于将给定节点 node 中的槽 slot8 迁移出节点,而后者用于将给定槽 slot8 导入到节点 node :

(1)、如果一个槽被设置为 MIGRATING 状态时,原本持有该槽的节点会继续接受关于这个槽的命令请求,但只有当键存在于该节点时,节点才会处理这个请求。如果命令所使用的键不存在于该节点,那么节点将向客户端返回一个 ASK 转向(redirection)错误,告知客户端,要将命令请求发送到槽的迁移目标节点。

(2)、如果一个槽被设置为 IMPORTING 状态时,节点仅在接收到 ASKING 命令之后,才会接受关于这个槽的命令请求。如果客户端向节点发送该槽的数据请求,命令为非 ASKING 时,那么节点会使用 MOVED 转向错误将命令请求转向至真正负责处理这个槽的节点。

举个例子来看看。

假设现在,我们有 A 和 B 两个节点,我们想将槽 8 从节点 A 移动到节点 B ,于是我们:

**(1)、向节点 B 发送命令 CLUSTER SETSLOT 8 IMPORTING A

(2)、向节点 A 发送命令 CLUSTER SETSLOT 8 MIGRATING B**

每当客户端向其他节点发送关于哈希槽 8 的命令请求时,这些节点都会向客户端返回指向节点 A 的转向信息(迁移中,虽然 A 和 B 都有槽 8 所对应的 key,但是各个节点仍然认为槽 8 由 A 负责,只有迁移结束后,槽 8 才属于 B 节点而不属于 A)。

(1)、如果命令要处理的键已经存在于槽 8 里面,那么这个命令将由节点 A 处理。

Redis Cluster原理初探

(2)、如果命令要处理的键未存在于槽 8 里面,那么这个命令由节点 B 处理。

Redis Cluster原理初探

这种机制将使得节点 A 不再创建关于槽 8 的任何新键。

与此同时,一个特殊的客户端 redis-trib 以及 Redis 集群配置程序(configuration utility)会将节点 A 中槽 8 里面的键移动到节点 B 。移动 key 的操作是原子性的,也就是一个 key 如果从 A 移动到 B,那么移动时,都不会出现 key 在 A 和 B 中同时出现。

内部数据结构

RedisCluster 涉及三个核心的数据结构 clusterState、clusterNode、clusterLink 都在 cluster.h 中定义。这三个数据结构中最重要的属性就是:clusterState.slots、clusterState.slots_to_keys 和 clusterNode.slots,它们保存了三种映射关系:

clusterState:集群状态

nodes:所有结点

migrating_slots_to:迁出中的槽

importing_slots_from:导入中的槽

slots_to_keys:槽中包含的所有 Key,用于迁移 Slot 时获得其包含的 Key

slots:Slot 所属的结点,用于处理请求时判断 Key 所在 Slot 是否自己负责 clusterNode:结点信息

slots:结点负责的所有 Slot,用于发送 Gossip 消息通知其他结点自己负责的 Slot。

clusterLink:与其他结点通信的连接

以下为这三个数据结构的定义:

Redis Cluster原理初探Redis Cluster原理初探Redis Cluster原理初探Redis Cluster原理初探

结合以上数据结构,我们看看客户端请求集群时,集群处理的的流程:

1、检查 key 所在 Slot 是否属于当前节点?

  • 1.1 计算 crc16(key) % 16384 得到 Slot
  • 1.2 查询 clusterState.slots 负责 Slot 的结点指针
  • 1.3 与 myself 指针比较

2、若不属于,则响应 MOVED 错误重定向客户端

3、若属于且 Key 存在,则直接操作,返回结果给客户端

4、若 Key 不存在,检查该 Slot 是否迁出中?(clusterState.migrating_slots_to)

5、若 Slot 迁出中,返回 ASK 错误重定向客户端到迁移的目的服务器上

6、若 Slot 未迁出,检查 Slot 是否导入中?(clusterState.importing_slots_from)

7、若 Slot 导入中且请求有 ASKING 标记,则直接操作

8、否则响应 MOVED 错误重定向客户端

本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。

原文链接:

https://mp.weixin.qq.com/s/zjwiOkRFvQDpKfeFL1-dUQ

评论

发布