写点什么

Redis 集群架构剖析 (2):槽位

作者:非晓为骁
  • 2022 年 3 月 18 日
  • 本文字数:2016 字

    阅读完需:约 7 分钟

在第一篇Redis集群架构剖析(1):认识cluster一篇中,我们对 cluster 有了初步的了解。知道如何启动一个集群,存储集群信息的数据结构长什么样的。虽然我们创建好了集群,但是集群的状态还是下线的。其实,这是因为集群中的三个节点,都没有负责处理任何槽位。也可以理解为,没有分配给集群节点,谁存什么键值范围的数据。在开始之前,依旧可以先思考下面的问题:


  • 如何给节点指派槽位的?

  • 节点是如何记录槽指派信息的?

  • 节点又是如何记录集群槽指派信息的?

  • 节点如何告诉其他节点,我的槽指派信息?



槽(slot)

  Redis 集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为 16384 个槽,数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点可以处理 0 个或最多 16384 个。


只有当数据库中的 16384 个槽都有节点在处理时,集群才处于上线状态。

槽指派

  通过向节点发送CLUSTER ADDSLOTS命令,我们可以将一个或多个槽指派给节点负责:


CLUSTER ADDSLOTS <slot> [slot...]
#例子127.0.0.1:6370 > CLUSTER ADDSLOTS 0 1 2 3 4 ... 5000
复制代码


  在三个节点都指派好槽位之后,集群进入上线状态。在讲完下面的数据结构之后,再讲一下ADDSLOTS的实现细节。


  槽指派可以通过 CLUSTER ADDSLOTS,也可以通过 redis-cli create cluster 创建集群时,自动分配槽位,见:

Redis 集群 docker 部署

槽指派信息数据结构

  clusterNode 结构的 slots 属性和 numslot 属性记录了节点负责处理哪些槽:


struct clusterNode {    //...    unsigned char slots[16384/8]         int numslots; //记录slots里为1的个数    //...}
复制代码


  slots 是一个二进制位数组(bit array),这个数组的长度为 16348/8=2048 个字节,共包含 16384 个二进制位。


  redis 以 0 为其实索引,16384 为终止索引,根据索引 i 上的二进制位的值来判断是否处理槽 i。


  • 如果 slots 数组在索引 i 上的二进制位的值为 1,那么表示节点负责处理槽 i。

  • 如果 slots 数组在索引 i 上的二进制位的值为 0,那么表示节点不负责处理槽 i。


  以下图为例,表示该节点负责处理 8,9,10,11,12,13,14,15 槽位,numslots 就为 8:



广播节点槽指派信息

  节点会告诉集群里的其他节点我当前存的槽位信息



  6371 收到 6370 的槽指派信息后,节点 6371 会在自己的 clusterState.nodes 字典中查找节点 6370 对应的 clusterNode 结构,并对结构中的 slots 数组进行保存或者更新。可以看到集群内的节点会互相发送消息,我现在的槽指派信息是哪些。这样子,集群中的每个节点都会知道数据库中的 16384 个槽分别被指派给了集群中的哪个节点。

集群记录槽指派信息

  如果只将槽指派信息保存在各个节点的 clusterNode.slots 数组里,效率来说不是很高,,通过遍历 clusterState.nodes 找到负责处理槽 i 的节点,时间复杂度为 O(N)。因此,clusterState 中存了一个全局的 slots,记录了集群中所有 16384 个槽的指派信息。


typedef struct clusterState {    //...    clusterNode *slots[16384];    //...}
复制代码


  每个项指向一个 clusterNode 结构的指针,如果为 NULL 表示还未分配,反之是一个 clusterNode 的话,就表示该槽已经分配。下图为集群分配好槽位后,clusterState.slots 的示意图。红线表示要查找槽位 5002 的路线图。



  clusterState.slots 数组记录了集群中所有槽的指派信息,而 clusterNode.slots 数组只记录 clusterNode 结构所代表的节点的槽位信息,这是这两个 slots 最大的区别。clusterNode.slots 存在的意义,在于当需要发送某个节点的槽指派信息给其他节点的时候,可以把整个节点的槽指派信息发送出去,而不是遍历 clusterState.slots 来拼接出来。

CLUSTER ADDSLOTS 命令的内部实现

可以先看一下CLUSTER ADDSLOTS的伪代码,看下具体在做什么


def CLUSTER_ADDSLOTS(*all_input_slots):    // 遍历所有输入槽,检查它们是否都是未指派的槽位  // 可以直接根据节点记录的集群槽指派信息来查  for i in all_input_slots:        // 只要有一个槽被分配了,那么会向客户端返回错误,并终止命令执行    if clusterState.slots[i] != NULL:        reply_error()                return                      // 如果槽都还未指派,那么再遍历所有输入槽,将这些槽位指派给当前节点    for i in all_input_slots:        // 设置clusterState结构的slots数组,对应index指向当前节点    clusterState.slots[i] = clusterState.myself        // 设置当前clusterNode的slots,将对应二进制位改成1    setSlotBit(clusterState.myself.slots, i)
复制代码


下图是对一个节点指派槽位 2 的示意图,根据伪代码,就是设置图中红色方框的位置。



CLUSTER ADDSLOTS命令执行完毕之后,节点会通过发送消息告知集群中的其他节点,自己目前正在负责处理的槽位。




在分配好所有的槽位之后,集群的状态从下线变成了上线,意味着,我们可以通过 redis-cli 和 redis-server 交互了。但是集群收到命令之后会怎么处理呢?请听下回分解。



系列文章

  1. Redis 集群架构剖析 (1):认识 cluster

用户头像

非晓为骁

关注

no pain no gain 2019.04.10 加入

用我的勤奋,一点一点地努力,提升自己的能力,拓展自己的视野,提高自己的认知。 我的知乎:https://www.zhihu.com/people/zhengfke

评论

发布
暂无评论
Redis集群架构剖析(2):槽位_redis集群_非晓为骁_InfoQ写作平台