uint64_t currentEpoch;
//这个数据也是记录配置纪元,只不过有些不同
//如果这个是从节点,记录的是正在复制主节点的配置纪元
//如果这个是主节点,记录的就是自己的配置纪元
uint64_t configEpoch;
//发送者的名字(ID)
char sender[REDIS_CLUSTER_NAMELEN];
//发送者目前的槽指派信息
unsigned char myslots[REDIS_CLUSTER_SLOTS/8];
//记录的是主节点名字
//如果这个是从节点,那么这里记录的是正在复制的主节点的名字
//如果这个是主节点,那么这里记录的是REDIS_NODE_NULL_NAME
//REDIS_NODE_NULL_NAME是一个40字节长,但值全为0的字节数组
char slaveof[REDIS_CLUSTER_NAMELEN];
//发送者的端口号
uint16_t port;
//发送者的标识值(主还是从,还是正在处于MEET等)
unsigned flags;
//发送者所处集群的状态(上线还是下线)
unsigned char state;
//消息的正文
union clusterMsgData data;
)clusterMsg;
可以看到使用的是data属性保存消息的正文,指向的是一个clusterMsgData结构(这个结构保存着所有5种消息,而每种消息都由对应的结构去储存)
typedef clusterMsgData(
//MEET、PING、PONG消息的正文
struct(
//每条MEET、PING、PONG消息都包含两个
//clusterMsgDataGossip结构
clusterMsgDataGossip gossip[1];
)
//FAIL消息的正文
struct(
clusterMsgDataFail about;
【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
开源分享完整内容戳这里
)fail
//PUBLISH消息的正文
struct(
clusterMsgDataPublish msg;
)
//…其他消息的正文
)
可以看到,在消息头里面,有着一系列发送者的信息,那么接收者就可以根据这些信息去对应找到clusterState.nodes里面的clusterNode结构,并且对应去更新里面的信息,比如修改里面的槽指派信息,还有flag标识。
Redis集群中的各个节点通过Gossip协议来交换各自关于不同节点的状态信息,其中Gossip协议由MEET、PING、PONG三种信息来实现,这三种消息的正文都由clusterMsgDataGossip结构组成的
union clusterMsgData(
//…
//MEET、PING、PONG消息的正文
struct(
//每条MEET、PING、PONG消息都包含两个clusterMsgDataGossip结构
clusterMsgDataGossip gossip[1];
)ping;
//其他消息的正文
)
这里可能就在想,那么如何区分MEET、PING、PONG消息呢?
这个是通过消息头里面的type属性来判断一条消息是MEET消息、PING消息还是PONG消息
每次发送MEET、PONG、PING消息时,发送者都从自己的已知节点列表中随机选出两个节点(可以是主节点也可以是从节点),并将这两个被选中节点的信息分别保存到clusterMsgDataGossip结构里面,所以每条MEET、PONG、PING消息都包含两个clusterMsgDataGossip结构
clusterMsgDataGossip结构记录了被选中节点的名字,发送者与被选中节点最后一次发送和接收PING消息和PONG消息的时间戳,被选中节点的IP地址和端口号,以及被选中节点的标识值
typedef struct clusterMsgDataGossip(
//被选中节点的名字
char nodename[REDIS_CLUSTER_NAMELEN];
//发送者最后一次向该节点发送PING消息的时间戳
uint32_t ping_sent;
//发送者最后一次从该节点接收PONG消息的时间戳
uint32_t pong_received;
//节点的IP地址
char ip[16];
//节点的端口号
uint16_t port;
//节点的标识值
uint16_t flags;
)clusterMsgDataGossip;
当接收者收到MEET、PING、PONG消息时,接收者会访问消息正文里面的两个clusterMsgDataGossip结构,并根据,然后对应去搜索自己的clusterState.node属性,看自己是否认识clusterMsgDataGossip里面的被选中的节点,然后对应进行下列的操作
如果被选中的节点不存在于接受者的已知节点链表里面,那么就代表接收者是第一次接触到被选中的节点,那么就要进行握手操作,接收者要根据结构中记录的IP地址和端口号等信息,与被选中的节点进行握手
如果被选中的节点存在于接收者的已知节点链表里面,那么说明接收者已经接触过被选中的节点,那么就要进行更新操作,接收者要根据结构中记录的信息,对被选中的节点进行一个状态更新,及更新对应的clusterNode结构
但对于MEET消息,是不是也应该要跟发送方进行握手呢?
当集群里的主节点A将主节点B标记为下线时,会通过集群广播一条关于主节点B的FAIL消息,所有接收到这条消息的节点(包括主从节点)都会将主节点B标记为下线
FAIL消息不使用Gossip协议去实现,因为Gossip协议在节点数量比较多的集群里面会有延迟(因为每次只告诉一个节点,而且只告诉发送者认知的两个节点的信息),但节点已下线消息需要实时性比较强,所以使用FAIL消息进行发送
FAIL消息是使用clusterMsgDataFail结构表示,而这个结构只包含一个nodename属性,用来记录已下线节点的名字
typedef struct clusterMsgDataFail(
char nodename[REDIS_CLUSTER_NAMELEN];
)clusterMsgDataFail
因为集群里的所有节点都有一个独一无二的名字(回看前面的文章,从节点的名字是ip地址加端口号),所以FAIL消息里面只需要保存下线节点的名字即可,接收到FAIL消息的节点就可以根据这个名字去判断哪个节点下线了,然后进行对应的下线操作(将节点标记为已下线)。
当客户端向集群中的某个主节点发送命令:
PUBLISH
那么接收到PUBLISH命令的主节点不仅会执行这条命令(即发送消息到对应的频道),还会通过集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行命令(即发送消息到对应的频道)
也就是说,上面的那条命令操控的不是一个主节点,而是整个主节点所在的集群,集群中的所有节点都会向指定的频道发送消息。
PUBLISH消息的正文由clusterMsgDataPublish正文表示
typedef struct(
//channel参数的长度(字节,通常一个英文或符号代表1字节)
uint32_t channel_len;
//msg参数的长度(字节,通常一个因为或符号代表1字节)
uint32_t message_len;
//保存的命令
//这里并不一定是8字符长度,按给的命令来计算的
//bulk:主体