Redis - 讲清楚集群模式(Redis Cluster)(上)
节点与集群构建
-
节点组成:Redis集群由多个独立节点组成,通过
CLUSTER MEET
命令实现节点握手,形成集群。 -
握手过程:
-
节点A为节点B创建
clusterNode
结构,发送MEET
消息。 -
节点B接收后创建节点A的
clusterNode
结构,返回PONG
消息。 -
节点A返回
PING
消息,完成握手。
-
之后,节点A会将节点B的信息通过Gossip协议传播给集群中的其他节点,让其他节点也与节点B进行握手,最终,经过一段时间之后,节点B会被集群中的所有节点认识。
-
数据结构:
-
clusterNode:
结构的link属性是一个 clusterLink 结构,该结构保存了连接节点所需的有关信息,比如套接字描述符,输人缓冲区和输出缓冲区:
struct clusterNode {// 创建节点的时间mstime_t ctime;// 节点的名字,由40个十六进制字符组成// 例如68eef66df23420a5862208ef5b1a7005b806f2ffchar name[REDIS_CLUSTER_NAMELEN];// 节点标识// 使用各种不同的标识值记录节点的角色(比如主节点或者从节点)// 以及节点目前所处的状态(比如在线或者下线)。int flags;// 节点当前的配置纪元,用于实现故障转移uint64_t configEpoch;// 节点的IP地址char iP[REDIS_IP_STR_LEN];// 节点的端口号int port;// 保存连接节点所需的有关信息clusterLink *link;// 负责的槽位(二进制位数组)unsigned char slots[16384 / 8]; int numslots; //... };
-
clusterLink
:clusterNode结构的link属性是一个clusterLink结构,该结构保存了连接节点所需的有关信息,比如套接字描述符,输人缓冲区和输出缓冲区.
typedef struct clusterLink {// 连接的创建时间mstime_t ctime; // TCP 套接字描述符int fd; // 输出缓冲区(保存待发送给其他节点的消息)sds sndbuf; // 输入缓冲区(保存从其他节点接收的消息)sds rcvbuf; // 关联的节点(NULL 表示未关联)struct clusterNode *node; } clusterLink;
-
clusterState
:每个节点都保存着一个 clusterState 结构,这个结构记录了在当前节点的视角下,集群目前所处的状态,例如集群是在线还是下线,集群包含多少个节点,集群当前的配置纪元,诸如此类。
-
typedef struct clusterState {// 当前节点自身的指针clusterNode *myself; // 集群当前配置纪元(用于故障转移)uint64_t currentEpoch; // 集群状态(在线/下线)int state; // 处理至少一个槽的节点数量int size; // 集群节点字典(键为节点ID,值为 clusterNode 结构)dict *nodes; // 所有槽的指派信息(数组索引为槽号,值为指向 clusterNode 的指针)clusterNode *slots[16384]; // 当前节点正在导入的槽(用于重新分片)clusterNode *importing_slots_from[16384]; // 当前节点正在迁移的槽(用于重新分片)clusterNode *migrating_slots_to[16384]; // 槽与键的映射关系(跳跃表,分值=槽号,成员=键名)zskiplist *slots_to_keys;
} clusterState;
槽指派与数据分片
-
槽总数:16384个槽,所有槽被指派后集群上线(
cluster_state:ok
)。 -
槽指派命令:
CLUSTER ADDSLOTS <slot>
将槽指派给节点。
127.0.0.1:7000>CLUSTER ADDSLOTS 0 1 2 3 4 ... 5000
OK
查看集群状态
127.0.0.1:7000>CLUSTER NODES
9dfb4c4e016e627d9769e4c9bb0d4fa208e65c26 127.0.0.1:7002 master - 0 1388317426165
-
槽信息存储(节点视角):
clusterNode.slots
(二进制位数组,记录自身负责的槽)。
slots属性 unsigned char
占用 1 个字节(8 位)的内存,所有位都用于表示数值,没有符号位。因此,其取值范围为 0 到 255,是一个二进制位数组(bitarray),这个数组的长度为16384 / 8=2048个字节,共包含16384个二进制位。
Redis以0为起始索引,16383为终止索引,对slots数组中的16384个二进制位进行编号,并根据索引i上的二进制位的值来判断节点是否负责处理槽1:
-
如果slots数组在索引i上的二进制位的值为1,那么表示节点负责处理槽i。
-
如果slots数组在索引i上的二进制位的值为0,那么表示节点不负责处理槽i。
展示了一个slots数组示例:这个数组索引0至索引7上的二进制位的值都为1,其余所有二进制位的值都为0,这表示节点负责处理槽0至槽7。
通过位运算检查槽指派,时间复杂度为O(1)
。
-
槽信息存储(集群视角):clusterstate结构中的slots数组记录了集群中所有16384个槽的指派信息(指针数组,直接定位槽所属节点)。查找时间复杂度为
O(1)
。
clusterstate.slots数组记录了集群中所有槽的指派信息,而clusterNode.slots数组只记录了clusterNode 结构所代表的节点的槽指派信息,多用于某个节点本身的槽信息发送给其他节点,这是两个slots数组的关键区别所在。
-
传播节点的槽指派信息:
一个节点除了会将自己负责处理的槽记录在clusterNode结构的slots属性和numslots属性之外,它还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。
当节点A通过消息从节点B那里接收到节点B的slots数组时,节点A会在自己的clusterstate.nodes
字典中查找节点B对应的clusterNode
结构,并对结构中的slots
数组进行保存或者更新。
因为集群中的每个节点都会将自己的slots数组通过消息发送给集群中的其他节点并且每个接收到slots数组的节点都会将数组保存到相应节点的 clusterNode
结构里面,因此,集群中的每个节点都会知道数据库中的16384个槽分别被指派给了集群中的哪些节点。