一、struct clusterNode
- clusterNode结构保存了一个节点的当前状态,比如节点的创建时间、节点的名字、节点 当前的配置纪元、节点的IP地址和端口号等等
- 每个节点都会使用一个clusterNode结构来记录自己的状态,并为集群中的所有其他节点 (包括主节点和从节点)都创建一个相应的clusterNode结构,以此来记录其他节点的状态:
struct clusterNode {
//创建节点的时间
mstime_t ctime;
//节点的名字,由40 个十六进制字符组成
//例如68eef66df23420a5862208ef5b1a7005b806f2ff
char name[REDIS_CLUSTER_NAMELEN];
//节点标识
//使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
//以及节点目前所处的状态(比如在线或者下线)。
int flags;
//节点当前的配置纪元,用于实现故障转移
uint64_t configEpoch;
//节点的IP 地址
char ip[REDIS_IP_STR_LEN];
//节点的端口号
int port;
//保存连接节点所需的有关信息
clusterLink *link;
// ...
};
二、struct clusterLink
- clusterNode结构的link属性是一个clusterLink结构,该结构保存了连接节点所需的有关信息,比如套接字描述符,输入缓冲区和输出缓冲区:
typedef struct clusterLink {
//连接的创建时间
mstime_t ctime;
// TCP 套接字描述符
int fd;
//输出缓冲区,保存着等待发送给其他节点的消息(message )。
sds sndbuf;
//输入缓冲区,保存着从其他节点接收到的消息。
sds rcvbuf;
//与这个连接相关联的节点,如果没有的话就为NULL
struct clusterNode *node;
} clusterLink;
- redisClient结构和clusterLink结构的相同和不同之处:
- redisClient结构和clusterLink结构都有自己的套接字描述符和输入、输出缓冲区,这两个结构的区别在于,redisClient结构中的套接字和缓冲区是用于连接客户端的,而clusterLink结构中的套接字和缓冲区则是用于连接节点的
三、struct clusterState
- 最后,每个节点都保存着一个clusterState结构,这个结构记录了在当前节点的视角下, 集群目前所处的状态,例如集群是在线还是下线,集群包含多少个节点,集群当前的配置纪元,诸如此类:
typedef struct clusterState {
//指向当前节点的指针
clusterNode *myself;
//集群当前的配置纪元,用于实现故障转移
uint64_t currentEpoch;
//集群当前的状态:是在线还是下线
int state;
//集群中至少处理着一个槽的节点的数量
int size;
//集群节点名单(包括myself 节点)
//字典的键为节点的名字,字典的值为节点对应的clusterNode 结构
dict *nodes;
// ...
} clusterState;
演示案例
- 以前一篇文章中介绍的7000、7001、7002三个节点为例,下图展示了节点7000创建的clusterState结构,这个结构从节点7000的角度记录了集群以及集群包含的三个节点的当前状态(为了空间考虑,图中省略了clusterNode结构的一部分属性):
- 结构的currentEpoch属性的值为0,表示集群当前的配置纪元为0
- 结构的size属性的值为0,表示集群目前没有任何节点在处理槽,因此结构的state属性的 值为REDIS_CLUSTER_FAIL,这表示集群目前处于下线状态
结构的nodes字典记录了集群目前包含的三个节点,这三个节点分别由三个clusterNode 结构表示,其中my self指针指向代表节点7000的clusterNode结构,而字典中的另外两个指针 则分别指向代表节点7001和代表节点7002的clusterNode结构,这两个节点是节点7000已知的 在集群中的其他节点
- 三个节点的clusterNode结构的flags属性都是REDIS_NODE_MASTER,说明三个节点都是主节点
- 节点7001和节点7002也会创建类似的clusterState结构:
- 不过在节点7001创建的clusterState结构中,my self指针将指向代表节点7001的 clusterNode结构,而节点7000和节点7002则是集群中的其他节点
- 而在节点7002创建的clusterState结构中,my self指针将指向代表节点7002的clusterNode 结构,而节点7000和节点7001则是集群中的其他节点
三、CLUSTER MEET命令的实现
- 通过向节点A发送CLUSTER MEET命令,客户端可以让接收命令的节点A将另一个节点B添加到节点A当前所在的集群里面:
CLUSTER MEET <ip> <port>
- 收到命令的节点A将与节点B进行握手(handshake),以此来确认彼此的存在,并为将来的进一步通信打好基础:
- ①节点A会为节点B创建一个clusterNode结构,并将该结构添加到自己的 clusterState.nodes字典里面
- ②之后,节点A将根据CLUSTER MEET命令给定的IP地址和端口号,向节点B发送一条 MEET消息(message)
- ③如果一切顺利,节点B将接收到节点A发送的MEET消息,节点B会为节点A创建一个 clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面
- ④之后,节点B将向节点A返回一条PONG消息
- ⑤如果一切顺利,节点A将接收到节点B返回的PONG消息,通过这条PONG消息节点A 可以知道节点B已经成功地接收到了自己发送的MEET消息
- ⑥之后,节点A将向节点B返回一条PING消息
- ⑦如果一切顺利,节点B将接收到节点A返回的PING消息,通过这条PING消息节点B可以知道节点A已经成功地接收到了自己返回的PONG消息,握手完成
图示
- 下图展示了以上步骤描述的握手过程
- 之后,节点A会将节点B的信息通过Gossip协议传播给集群中的其他节点,让其他节点也 与节点B进行握手,最终,经过一段时间之后,节点B会被集群中的所有节点认识