Redis 集群架构剖析 (2):槽位
在第一篇Redis集群架构剖析(1):认识cluster一篇中,我们对 cluster 有了初步的了解。知道如何启动一个集群,存储集群信息的数据结构长什么样的。虽然我们创建好了集群,但是集群的状态还是下线的。其实,这是因为集群中的三个节点,都没有负责处理任何槽位。也可以理解为,没有分配给集群节点,谁存什么键值范围的数据。在开始之前,依旧可以先思考下面的问题:
如何给节点指派槽位的?
节点是如何记录槽指派信息的?
节点又是如何记录集群槽指派信息的?
节点如何告诉其他节点,我的槽指派信息?
槽(slot)
Redis 集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为 16384 个槽,数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点可以处理 0 个或最多 16384 个。
只有当数据库中的 16384 个槽都有节点在处理时,集群才处于上线状态。
槽指派
通过向节点发送CLUSTER ADDSLOTS
命令,我们可以将一个或多个槽指派给节点负责:
在三个节点都指派好槽位之后,集群进入上线状态。在讲完下面的数据结构之后,再讲一下ADDSLOTS
的实现细节。
槽指派可以通过 CLUSTER ADDSLOTS,也可以通过 redis-cli create cluster 创建集群时,自动分配槽位,见:
槽指派信息数据结构
clusterNode 结构的 slots 属性和 numslot 属性记录了节点负责处理哪些槽:
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 个槽的指派信息。
每个项指向一个 clusterNode 结构的指针,如果为 NULL 表示还未分配,反之是一个 clusterNode 的话,就表示该槽已经分配。下图为集群分配好槽位后,clusterState.slots 的示意图。红线表示要查找槽位 5002 的路线图。
clusterState.slots 数组记录了集群中所有槽的指派信息,而 clusterNode.slots 数组只记录 clusterNode 结构所代表的节点的槽位信息,这是这两个 slots 最大的区别。clusterNode.slots 存在的意义,在于当需要发送某个节点的槽指派信息给其他节点的时候,可以把整个节点的槽指派信息发送出去,而不是遍历 clusterState.slots 来拼接出来。
CLUSTER ADDSLOTS 命令的内部实现
可以先看一下CLUSTER ADDSLOTS
的伪代码,看下具体在做什么
下图是对一个节点指派槽位 2 的示意图,根据伪代码,就是设置图中红色方框的位置。
在CLUSTER ADDSLOTS
命令执行完毕之后,节点会通过发送消息告知集群中的其他节点,自己目前正在负责处理的槽位。
在分配好所有的槽位之后,集群的状态从下线变成了上线,意味着,我们可以通过 redis-cli 和 redis-server 交互了。但是集群收到命令之后会怎么处理呢?请听下回分解。
系列文章
评论