【Redis】Redis集群架构剖析(2):槽位
本文档主要是学习redis cluster的一下学习笔记和想法,通过这篇文章,希望你能了解redis的cluster是如何添加分配槽位的,以及里面的数据结构是怎么样的。
在第一篇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创建集群时,自动分配槽位,见:
槽指派信息数据结构
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交互了。但是集群收到命令之后会怎么处理呢?请听下回分解。
系列文章
更多推荐
所有评论(0)