Redis集群通过分片的方式来保存数据库的键值对:集群的整个数据库被分为16384个槽,数据库中的每个键都输入这16384个槽其中的一个,集群中的每个节点都可以处理0-16384个槽。
当数据库中的16384个槽都有节点在处理时,集群处于上线状态;如果有任何一个槽没有得到处理,那么集群处于下线状态。
//通过向节点发送CLUSTER ADDSLOTS 命令,可以将一个或者多个槽指派给节点负责 CLUSTER ADDSLOTS <slot><slot...>
记录节点的槽指派信息
clausterNode结构的slouts属性和numslot属性记录了节点负责处理哪些槽:
struct clusterNode{ //.... unsigned char slots[16384/8]; //该集群节点负责处理槽的数量 int numslots; }
slots属性是一个二进制数组,数组长度为16384/8=2048位,包含16384个二进制位。
Redis以0位起始索引,16383为终止索引,对slots数组中的16384个二进制位进行编号,并根据索引i上的值来判断节点是否负责处理i:
-
- 如果负责处理槽i,那么slots数组在索引i上的二进制的值为1
- 如果不负责处理槽i,那么slots数组在索引i上的二进制的值为0
传播节点的槽指派信息
一个节点除了会将自己负责处理的槽记录在clusterNode结构的slots属性和numslots属性之外,它还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责哪些槽,其他节点会做出相应的改变。
例如:当节点A通过消息从节点B那里接收到节点B的slots数组时,节点A会在自己的clusterState.node字典中查到节点B对应的clusterNode结构,并对结构中的slots数组进行保存或者更新。
因为集群中的每个节点都会将自己的slots数组通过消息发送给集群中的其他节点,并且每个接收到slots数组的节点都会将数组的节点保存到相应节点的clusterNode结构里面,因此,集群中的每个节点都会知道数据库中的16384个槽分别指派给了集群中的哪些几点。
记录集群所有槽的指派信息
clusterState结构中的slots数组记录了集群中所有16384个槽的指派信息:
typedef struct clusterstate{ clusterNode * slots[16384]; }
slots数组包含16384个项,每个数组项都都是一个指向clusterNode结构的指针:
- 如果slots[i]指针指向null,那么表示槽i尚未指派给任何节点
- 如果slots[i]指针指向一个clusterNode结构,那么表示槽i已经指派给了clusterNode结构所代表的节点。
如果只将槽指派信息保存在各节点的clusterNode.slots数组里,会出现一些无法搞笑解决的问题,而clusterState.slots数组的存在解决了这些问题
- 如果节点只使用clusterNode.slots数组来记录槽的指派信息,那么为了知道槽i是否已经被指派,或者槽i被指派到了哪个节点,程序需要遍历ClusterState.Node里面的所有clusterNode结构,检查这些ClusterNode结构的slots数组,直到找到负责处理槽i的节点为止。
- 而通过将所有槽的指派信息保存到clusterState.node数组里面,程序要检查槽i是否被指派,又或者取得负责处理i的节点,只需要范围clusterState.slots[i]的值即可,操作复杂的仅为O(1)
虽然clusterState.slots数组记录了集群中所有槽的指派信息,但是使用clusterNode结构的slots数组来记录单个节点的槽指派信息仍然是有必要的:
- 如果不使用clusterNode.slots数组,而单独使用clusterState.slots数组的话,每次要将某个节点A的槽指派信息传播给其他节点时,程序必须要先遍历整个clusterState.slots数组,记录节点A负责处理哪些槽,然后才能发送节点A的槽指派信息,这比直接发送clusterNode.slots数组要低效和麻烦的多。
总结:clusterState.slots数组记录了集群中所有槽的指派信息,而clusterNode.slots数组只记录了clusterNode结构所代表的节点槽指派信息。
CLUSTER ADDSLOTS 命令的实现
CLUSTER ADDSLOTS 命令接收一个或多个槽作为参数,并将所有输入的槽指派给接收该命令的节点负责
CLUSTER ADDSLOTS <SLOT>[slot ........]
CLUSTER ADDSLOTS命令的基本逻辑
- 首先遍历搜索的输入槽,检查它们是否都是未指派槽,如果有一个槽被指派了某个节点,就终止命令并返回错误信息
- 如果都是未指派槽,那么再次遍历所有输入槽,将这些槽指派给当前节点
-
- 将clusterState.slots[i]的指针指向当前节点的clusterNode结构
- 将数组在索引i上的二进制位都设置为 1
clusterState[i] = clusterState.myself setSlotBit(clusterState.myself.slots,i);
最后,在CLUSTER ADDSLOTS 命令执行完毕之后,节点会通过发送消息告知集群中的其他节点,自己目前在处理哪些槽