在集群的原生创建中,我们知道只需要在一个节点上执行cluster meet命令就可以做到节点间的相互发现.今天我们就看下节点的发现过程以及节点间通信协议是什么样的.
一. gossip协议
Redis Cluster 集群的各节点间是采用gossip协议进行节点发现和通信.
gossip协议简单点说就是,我得到的信息是其他节点告诉我的,同时我也将我的信息和我知道的其他节点信息告诉别人,使整个集群达到最终一致.
二. MEET
看下cluster meet命令执行过程
1.首先调用clusterStartHandshake()方法,准备节点间握手.
2.判断当前是否已经在执行握手,已经在执行则不继续操作.
3.创建节点信息(clusterNode)并存储在server.cluster->nodes中.
4.这时该节点信息中并没有相关的link信息;节点标识状态node->flags 设置为CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_MEET,该节点并不是一个可用状态.
这个流程中只保存了节点信息, 节点间通信流程是通过定时任务clusterCron()完成,通信报文可以参考PING&PONG部分.
三. 定时任务
Redis设计了一套定时任务进行节点间通信.
1. 集群中节点通信是使用每秒执行10次的定时任务clusterCron()完成.
2. 定时任务会根据link和flags信息向其他节点间发送两种信息: MEET和PING
3. PING与MEET会随机选择其他节点信息作为流言信息(gossip)发送出去.
4. 节点消息体的封装和发送都会由clusterSendPing()方法进行执行;
5. 用于接收消息的端口并不是提供服务的6379端口,而是RCmb (Redis Cluster message bus)端口,默认是在服务端口上加10000的16379集群端口.
6. 对方节点通过clusterReadHandler()方法收到消息后,更新自己的server.cluster->nodes相关信息,同时会返回一个同样数据结构的PONG信息.
下文会详细说明下交互报文,也就清楚每次交互发送了哪些信息,收到哪些信息.
四. 报文协议
报文体分为两部分:报文头和报文体
报文头是自身节点信息;
报文体会有4种,后文会逐一说明.
(1) PING, MEET 和PONG
(2) FAIL
(3) PUBLISH
(4) UPDATE
报文头
主要包含自身节点的信息:port,cport,配置版本号,hash槽信息等.
typedef struct {
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
uint32_t totlen; /* Total length of this message */
uint16_t ver; /* Protocol version, currently set to 1. */
uint16_t port; /* TCP base port number. */
uint16_t type; /* Message type */
uint16_t count; /* Only used for some kind of messages. */
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
uint64_t configEpoch; /* The config epoch if it's a master, or the last
epoch advertised by its master if it is a
slave. */
uint64_t offset; /* Master replication offset if node is a master or
processed replication offset if node is a slave. */
char sender[CLUSTER_NAMELEN]; /* Name of the sender node */
unsigned char myslots[CLUSTER_SLOTS/8];
char slaveof[CLUSTER_NAMELEN];
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
char notused1[34]; /* 34 bytes reserved for future usage. */
uint16_t cport; /* Sender TCP cluster bus port */
uint16_t flags; /* Sender node flags */
unsigned char state; /* Cluster state from the POV of the sender */
unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
union clusterMsgData data;
} clusterMsg;
报文体
报文体是使用共用体(union)定义,会根据发送消息的不同消息格式也不相同.
union clusterMsgData {
/* PING, MEET and PONG */
struct {
/* Array of N clusterMsgDataGossip structures */
clusterMsgDataGossip gossip[1];
} ping;
/* FAIL */
struct {
clusterMsgDataFail about;
} fail;
/* PUBLISH */
struct {
clusterMsgDataPublish msg;
} publish;
/* UPDATE */
struct {
clusterMsgDataUpdate nodecfg;
} update;
};
五. 节点间交互信息
5.1 PING & PONG
上文已经阐述了节点间会不停的互相发送PING&PONG心跳包,交互各节点间的状态.
具体报文体是一个最大长度为2的clusterMsgDataGossip[]数组;
如果有PFAIL状态的节点信息,也会追加到消息后面,一同发出去.
结构体内容为当前服务节点缓存的其他节点的信息
typedef struct {
char nodename[CLUSTER_NAMELEN];
uint32_t ping_sent;
uint32_t pong_received;
char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */
uint16_t port; /* base port last time it was seen */
uint16_t cport; /* cluster port last time it was seen */
uint16_t flags; /* node->flags copy */
uint32_t notused1;
} clusterMsgDataGossip
5.2 PFAIL & FAIL
与sentinel模式相似PFAIL和FAIL 分别表示一个节点服务的主观宕机和客观宕机.它们是根据各节点间的PING,PONG信息进行传递交互的,具体流程如下:
1. 集群中当一个节点向另一个节点发送PING命令,但是目标节点未在给定的时限(node timeout)内返回PONG时,那么发送命令的节点会将目标节点标记为PFAIL(possibly failing).
2. 为了保证链接有效,在超过(timeout/2)的时间内还没收到回复时,会重新建立链接,再次发送PING信息.
3. 当节点接收到其它节点发送来的信息时,它会记下哪些被其它节点标记为失效的节点,这称之为失效报告(failure report);
4. 如果当前节点已经将某个节点标记为PFAIL,并且根据所收到的失效报告显示,集群中的大部分其它主节点也认为该节点进入了PFAIL状态,那么节点将会把哪个节点标记为失效状态FAIL;
5.一旦某个节点被标记为FAIL,关于这个节点已失效的信息就会通过clusterSendFail()方法广播到整个集群中,所有接收到这条信息的节点都会将失效节点标记为FAIL;
此时发送的报文头clusterMsg->type值为:CLUSTERMSG_TYPE_FAIL,
报文体是struct clusterMsgDataFail结构.
typedef struct {
char nodename[CLUSTER_NAMELEN];
} clusterMsgDataFail;
5.3 PUB/SUB
在cluster模式下进行pub/sub时,是将pub/sub channel和pub/sub内容信息发送到一个节点后,由该节点组装报文对集群中其他节点进行广播,将pub/sub信息转发出去.
报文头clusterMsg->type值为CLUSTERMSG_TYPE_PUBLISH
报文体部分的结构是clusterMsgDataPublish
typedef struct {
uint32_t channel_len;
uint32_t message_len;
/* We can't reclare bulk_data as bulk_data[] since this structure is
* nested. The 8 bytes are removed from the count during the message
* length computation. */
unsigned char bulk_data[8];
} clusterMsgDataPublish;
如执行命令: publish topic msg
数据格式:
5.4 配置版本变更
当节点发现接收到的报文头中的configepoch低于自己的时候,会通过clusterSendUpdate()方法,将自己的hash槽信息组装成报文发送回sender节点,进行hash槽信息同步.
typedef struct {
uint64_t configEpoch; /* Config epoch of the specified instance. */
char nodename[CLUSTER_NAMELEN]; /* Name of the slots owner. */
unsigned char slots[CLUSTER_SLOTS/8]; /* Slots bitmap. */
} clusterMsgDataUpdate;
综上,我们了解了节点间的信息通信与交互.