对于cluster.c的源码分析,我将会分两部分介绍。本文主要分析集群通信和通信故障。
先大致归纳下cluster的主要函数
void clusterCron(void);//集群的定时任务
int clusterProcessPacket(clusterLink *link);//消息处理中心
void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link);//根据消息中的gossip信息更新nodes里的节点
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);//接收集群中节点的连接
void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask);//读处理函数
void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask);//写处理函数
void clusterSendPing(clusterLink *link, int type);//发送ping,pong,meet消息给指定节点,同时将gossip信息(集群其他节点的存活状况)更新给指定的节点
void clusterSendFail(char *nodename);//发送fail消息给所有连接的节点
void clusterSendFailoverAuth(clusterNode *node);//发送FailoverAuth消息给指定节点,给指定节点投票
void clusterSendMFStart(clusterNode *node);//发送MFStart消息给指定节点
void clusterSendUpdate(clusterLink *link, clusterNode *node);//发送关于node的update信息给节点,去更新指定节点的槽信息
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);//判断是否需要给发送投票,如果可以就给发送者投票
void clusterHandleSlaveFailover(void);//处理failover
void clusterHandleSlaveMigration(int max_slaves);//处理slave迁移
void resetManualFailover(void);//在启动或终止failover时,进行初始化
void clusterDoBeforeSleep(int flags);
clusterNode *clusterLookupNode(char *name);//根据节点名字在nodes中获取节点
void clusterSetMaster(clusterNode *n);//将节点设置为master,如果自身是master,则转变成slave
void clusterSetNodeAsMaster(clusterNode *n);
int clusterNodeAddSlave(clusterNode *master, clusterNode *slave);
clusterNode *createClusterNode(char *nodename, int flags);
int clusterAddNode(clusterNode *node);
void clusterDelNode(clusterNode *delnode);
int clusterAddSlot(clusterNode *n, int slot);
int clusterDelSlot(int slot);
int clusterDelNodeSlots(clusterNode *node);
void clusterCloseAllSlots(void);
集群通信
集群中的各个节点通过发送和接收信息来进行通信,我们把发送消息的节点叫做发送者,接收消息信息节点
叫做接收者。发送的信息主要有以下九种:
1、MEET消息:当发送者接收到客户端发送的cluster meet命令时,发送者会向接收者发送meet消息,请求接收加入到发送者所在的集群里。
2、PING消息:集群里用来检测相应节点是否在线的消息,每个节点默认每隔一秒从节点列表随机选5个节点,然后对最长时间没有收到PONG回复的节点发送PING消息。此外,如果上次收到某个节点的PONG消息回复的时间,距离当前时间超过了cluster-node-time选项设置的一半,那么会对此节点发送PING消息,这样可以防止节点长期没被随机到,而导致长期没有发送PING检测是否存活。
3、PONG消息:当接收者收到发送者发来的meet消息或者ping消息时,为了向发送者确认这条meet消息或ping消息已到达,接收者向发送者返回一条pong消息。另外,一个节点也可以通过向集群广播自己的pong消息来让集群中的其他节点立即刷新关于这个节点的认识、。
4、FAIL消息:当一个主节点a判断另外一个主节点b已经进入fail状态时,节点a向集群广播一条关于节点b的fail消息,所有收到这条消息的节点都会立即将节点b标记为已下线。
5、PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广播一条PUBLISH消息,所有接收到PUBLISH消息的节点都会执行相同的PUBLISH命令。
6、FAILOVER_AUTH_REQUEST消息:当slave的master进入fail状态,slave向集群中的所有的节点发送消息,但是只有master才能给自己投票failover自己的maser。
7、FAILOVER_AUTH_ACK消息:当master接收到FAILOVER_AUTH_REQUEST消息,如果发送者满足投票条件且自己在当前纪元未投票就给它投票,返回FAILOVER_AUTH_ACK消息.。
8、UPDATE消息:当接收到ping、pong或meet消息时,检测到自己与发送者slots不一致,且发送的slots的纪元过时, 就发送slots中纪元大于发送者的节点信息作为update消息的内容给发送者。
9、MFSTART消息:当发送者接收到客户端发送的cluster failover命令时,发送者会向自己的master发送MFSTART消息,进行手动failover。
clusterProcessPacket是cluster的消息处理中心,来负责处理集群中的MEET,PING,FAIL,PUBLISH,FAILOVER_AUTH_REQUEST,FAILOVER_AUTH_ACK,UPDATE,MFSTART这几种消息。(吐槽一下,clusterProcessPacket函数长达几百行看着眼花缭乱)
int clusterProcessPacket(clusterLink *link) {
……
//检查发送者是否已知节点且是否处于handshake状态
sender = clusterLookupNode(hdr->sender);
if (sender && !nodeInHandshake(sender)) {
//根据消息更新发送者在此节点的上的配置纪元信息和此节点的当前纪元
if (senderCurrentEpoch > server.cluster->currentEpoch)
server.cluster->currentEpoch = senderCurrentEpoch;
if (senderConfigEpoch > sender->configEpoch)
sender->configEpoch = senderConfigEpoch;
//主从复制的offset
sender->repl_offset = ntohu64(hdr->offset);
sender->repl_offset_time = mstime();
//更新全局的主从复制offset
if (server.cluster->mf_end &&nodeIsSlave(myself) &&myself->slaveof == sender
&&hdr->mflags[0] & CLUSTERMSG_FLAG0_PAUSED &&server.cluster->mf_master_offset == 0)
{
server.cluster->mf_master_offset = sender->repl_offset;
}
}
if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_MEET) {
//通过ping消息和meet消息中来设置myself的地址
if ((type == CLUSTERMSG_TYPE_MEET || myself->ip[0] == '\0') &&server.cluster_announce_ip == NULL)
{
……
memcpy(myself->ip,ip,NET_IP_STR_LEN);
……
}
if (!sender && type == CLUSTERMSG_TYPE_MEET) {
//如果是meet消息,为发送者创建clusterNode结构,然后加入clusterState.nodes
node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE);
……
clusterAddNode(node);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
}
if (!sender && type == CLUSTERMSG_TYPE_MEET)//发送者是个未知节点并且是meet消息
clusterProcessGossipSection(hdr,link);//将消息gossip信息中的节点更新到自己nodes字典中
clusterSendPing(link,CLUSTERMSG_TYPE_PONG);
}
if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG ||type == CLUSTERMSG_TYPE_MEET)
{
if (link->node) {
//发送者还处于握手状态
if (nodeInHandshake(link->node)) {
//如果我们已经有这个节点,根据消息尝试更新这个节点的地址
if (sender) {
if (nodeUpdateAddressIfNeeded(sender,link,hdr))
……
}
……
} else if (memcmp(link->node->name,hdr->sender,CLUSTER_NAMELEN) != 0)
{//如果消息中节点的名字和link中的名字不匹配,删除link
……
freeClusterLink(link);
……
}
}
if (link->node && type == CLUSTERMSG_TYPE_PONG) {
//收到PONG信息,更新节点信息,删除CLUSTER_NODE_PFAIL或CLUSTER_NODE_FAIL状态
if (nodeTimedOut(link->node)) {
link->node->flags &= ~CLUSTER_NODE_PFAIL;
} else if (nodeFailed(link->node)) {
clearNodeFailureIfNeeded(link->node);
}
}
//检查槽信息是否一致
if (sender) {
sender_master = nodeIsMaster(sender) ? sender : sender->slaveof;
if (sender_master) {
dirty_slots = memcmp(sender_master->slots,hdr->myslots,sizeof(hdr->myslots)) != 0;
}
}
//发送者是master,且槽信息不一致,更新本地的槽信息
if (sender && nodeIsMaster(sender) && dirty_slots)
clusterUpdateSlotsConfigWith(sender,senderConfigEpoch,hdr->myslots);
//更新发送者过时的槽信息
if (sender && dirty_slots) {
for (j = 0; j < CLUSTER_SLOTS; j++) {
if (bitmapTestBit(hdr->myslots,j)) {
if (server.cluster->slots[j] == sender||server.cluster->slots[j] == NULL) continue;
if (server.cluster->slots[j]->configEpoch >senderConfigEpoch)
{ clusterSendUpdate(sender->link,server.cluster->slots[j]);
break;
}
}
}
}
//处于槽信息冲突
if (sender &&nodeIsMaster(myself) && nodeIsMaster(sender)
&&senderConfigEpoch == myself->configEpoch)
{
clusterHandleConfigEpochCollision(sender);
}
//根据msg的gossip信息更新nodes字典中的节点信息(添加节点或者更新节点的在线状态)
if (sender) clusterProcessGossipSection(hdr,link);
} else if (type == CLUSTERMSG_TYPE_FAIL) {
clusterNode *failing;
if (sender) {
failing = clusterLookupNode(hdr->data.fail.about.nodename);
if (failing &&!(failing->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_MYSELF)))
{ //将发送者节点更新FAIL
failing->flags |= CLUSTER_NODE_FAIL;
……
}
}
} else if (type == CLUSTERMSG_TYPE_PUBLISH) {
if (dictSize(server.pubsub_channels) ||listLength(server.pubsub_patterns))
{ ……//向订阅的用户群发消息
pubsubPublishMessage(channel,message);
……
}
} else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST) {
//处理FAILOVER_AUTH_REQUEST消息,check是否可以给其投票
clusterSendFailoverAuthIfNeeded(sender,hdr);
} else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK) {
//处理FAILOVER_AUTH_ACK消息
//发送者是master,其节点有槽,并且当前纪元大于当前的票选纪元,投票成功
……
server.cluster->failover_auth_count++;
} else if (type == CLUSTERMSG_TYPE_MFSTART) {
//当我是master,发送者是我的slave,才可以启动手动failover
resetManualFailover();
……
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
//根据消息中的nodename更新对应节点的configEpoch,slots
……
clusterUpdateSlotsConfigWith(n,reportedConfigEpoch,hdr->data.update.nodecfg.slots);
}
……
}
clusterProcessGossipSection主要是在接收到meet,pong消息的时候,将消息中的gossip信息更新到自己的
nodes字典中,判断是否有节点已经处于FAIL状态。
void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
uint16_t count = ntohs(hdr->count);
clusterMsgDataGossip *g = (clusterMsgDataGossip*) hdr->data.ping.gossip;
clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender);
while(count--) {
……
node = clusterLookupNode(g->nodename);
if (node) {
//根据消息中的clusterMsgDataGossip更新节点状态
if (sender && nodeIsMaster(sender) && node != myself) {
if (flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) {
if (clusterNodeAddFailureReport(node,sender)) {//在这个节点的fail_reports里加入sender
}
markNodeAsFailingIfNeeded(node);//判断在这个节点的fail_reports,将节点标记为fail
} else {
if (clusterNodeDelFailureReport(node,sender)) {//在这个节点的fail_reports里删除sender
}
}
}
……
} else {//节点不存在,开始handshake(主要是接收到meet消息,与gossip中的节点进行handshake)
if (sender &&!(flags & CLUSTER_NODE_NOADDR) &&!clusterBlacklistExists(g->nodename))
{
clusterStartHandshake(g->ip,ntohs(g->port),ntohs(g->cport));
}
}
g++;
}
}
集群通信故障
集群故障检测主要是在定时任务clusterCron定期向随机节点和长期没被随机到的节点发送ping,然后根据返回
pong的时间判断是疑似下线(PFAIL)。集群通过ping和pong消息将节点知道nodes的在线状态传播到其他节点。当
检测到某个节点的fail_reports大于等于(server.cluster->size / 2) + 1时,标记这个节点为FAIL,然后广播FAIL消息到整
个集群。slave中定时任务clusterCron检测自己的master为FAIL,就启动Failover。首先开始选举,根据与master
的offset偏差进行排序决定谁优先请求被投票。投票完成后,票数大于等于(server.cluster->size / 2) + 1的节点成为
master,如果没有大于(server.cluster->size / 2) + 1,等待下次投票。
clusterCron是集群的定时任务,根据server .cluster->nodes的节点状态信息作出相应的处理,起到监控作用。
1、对handshake的节点创建连接,同时删除handshake超时的节点;
2、向随机节点和now-pong_received>cluster_node_timeout/2的发送ping消息;
3、遍历nodes检查有没有超时还没返回pong的节点,然后标记为pfail的节点(之后通过gossip消息将pfail的节点
传播给别的节点,clusterProcessGossipSection函数中更新gossip消息,如果当某个节点的failreport超过
(server.cluster->size / 2) + 1);
4、统计孤立master,判断是否需要slave迁移,以避免孤立master fail没有slave failover;
5、节点是slave,判断是否进行手动failover或者failover;
void clusterCron(void) {
……
di = dictGetSafeIterator(server.cluster->nodes);
//遍历nodes字典中的节点,检查与节点的连接情况并处理
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR)) continue;
//节点handshake超时,删除节点
if (nodeInHandshake(node) && now - node->ctime > handshake_timeout) {
clusterDelNode(node);
continue;
}//新加入字典的节点,创建连接,并发送ping或者meet
if (node->link == NULL) {
……
fd = anetTcpNonBlockBindConnect(server.neterr, node->ip,node->cport, NET_FIRST_BIND_ADDR);
link = createClusterLink(node);
link->fd = fd;
node->link = link;
aeCreateFileEvent(server.el,link->fd,AE_READABLE,clusterReadHandler,link);
old_ping_sent = node->ping_sent;
clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?CLUSTERMSG_TYPE_MEET
: CLUSTERMSG_TYPE_PING);
if (old_ping_sent) {
node->ping_sent = old_ping_sent;
}
node->flags &= ~CLUSTER_NODE_MEET;
}
}
//每隔一秒从节点列表随机选5个节点,然后对最老回复PONG的时间的节点发送PING消息
if (!(iteration % 10)) {
int j;
for (j = 0; j < 5; j++) {
de = dictGetRandomKey(server.cluster->nodes);
clusterNode *this = dictGetVal(de);
if (this->link == NULL || this->ping_sent != 0) continue;
if (this->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_HANDSHAKE))
continue;
if (min_pong_node == NULL || min_pong > this->pong_received) {
min_pong_node = this;
min_pong = this->pong_received;
}
}
if (min_pong_node) {
clusterSendPing(min_pong_node->link, CLUSTERMSG_TYPE_PING);
}
}
di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
……
if (nodeIsSlave(myself) && nodeIsMaster(node) && !nodeFailed(node)) {
int okslaves = clusterCountNonFailingSlaves(node);
if (okslaves == 0 && node->numslots > 0 &&
node->flags & CLUSTER_NODE_MIGRATE_TO)
{ //记录孤立master的数量
orphaned_masters++;
}//记录最大可用slaves的数量
if (okslaves > max_slaves) max_slaves = okslaves;
if (nodeIsSlave(myself) && myself->slaveof == node)
this_slaves = okslaves;//如果我是slave记录我的master的可用slave数量
}
if (node->link && now - node->link->ctime >server.cluster_node_timeout &&node->ping_sent &&
node->pong_received < node->ping_sent && now - node->ping_sent > server.cluster_node_timeout/2)
{//当节点超过cluster_node_timeout/2的时间还没回复pong,断开连接
freeClusterLink(node->link);
}
if (node->link &&node->ping_sent == 0 &&(now - node->pong_received) > server.cluster_node_timeout/2)
{//如果距离上次收到PONG消息回复的时间超过cluster_node_timeout/2,发送ping消息
clusterSendPing(node->link, CLUSTERMSG_TYPE_PING);
continue;
}
if (server.cluster->mf_end &&nodeIsMaster(myself) &&server.cluster->mf_slave == node &&node->link)
{//给请求手动failover的slave发送ping消息
clusterSendPing(node->link, CLUSTERMSG_TYPE_PING);
continue;
}
if (node->ping_sent == 0) continue;
delay = now - node->ping_sent;
if (delay > server.cluster_node_timeout) {
//给节点发送ping消息,超过cluster_node_timeout没收到该节点的pong回复,将节点标记为PFAIL
if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) {
node->flags |= CLUSTER_NODE_PFAIL;
update_state = 1;
}
}
}
dictReleaseIterator(di);
if (nodeIsSlave(myself) &&server.masterhost == NULL &&myself->slaveof &&nodeHasAddr(myself->slaveof))
{//如果我是slave,而且我没开启主从复制,则开启主从复制
replicationSetMaster(myself->slaveof->ip, myself->slaveof->port);
}
manualFailoverCheckTimeout();//检查手动failover是否超时
if (nodeIsSlave(myself)) {
clusterHandleManualFailover();//处理手动failover
clusterHandleSlaveFailover();//处理failover
//存在孤立master,且我所在的主从的OKslave最大同时大于,启动从节点迁移
if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves)
clusterHandleSlaveMigration(max_slaves);
}
if (update_state || server.cluster->state == CLUSTER_FAIL)
clusterUpdateState();//更新集群状态
}
clusterHandleSlaveFailover检查master是否下线,然后从节点开始Failover,请求被投票获取授权,获取授权后提升为master。
void clusterHandleSlaveFailover(void) {
……
if (nodeIsMaster(myself) ||myself->slaveof == NULL ||(!nodeFailed(myself->slaveof) && !manual_failover) ||myself->slaveof->numslots == 0)
{//myself->slaveof的flag为FAIL,已经下线
server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE;
return;
}
//距离上次尝试failover的时间>cluster_node_timeout*4,启动failover
if (auth_age > auth_retry_time) {
server.cluster->failover_auth_time = mstime() +500 + random() % 500;
server.cluster->failover_auth_count = 0;
server.cluster->failover_auth_sent = 0;
server.cluster->failover_auth_rank = clusterGetSlaveRank();
server.cluster->failover_auth_time +=server.cluster->failover_auth_rank * 1000;
//根据offset的偏差排序,偏差越小可以优先请求被投票
if (server.cluster->mf_end) {//手动failover,可以优先请求被投票
server.cluster->failover_auth_time = mstime();
server.cluster->failover_auth_rank = 0;
}
clusterBroadcastPong(CLUSTER_BROADCAST_LOCAL_SLAVES);
return;
}//还没请求被投票,向整个集群请求被投票
if (server.cluster->failover_auth_sent == 0) {
server.cluster->currentEpoch++;
server.cluster->failover_auth_epoch = server.cluster->currentEpoch;
clusterRequestFailoverAuth();
server.cluster->failover_auth_sent = 1;
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_FSYNC_CONFIG);
return; /* Wait for replies. */
}
//检查自己得到票数是否达到needed_quorum
if (server.cluster->failover_auth_count >= needed_quorum) {
if (myself->configEpoch < server.cluster->failover_auth_epoch) {
myself->configEpoch = server.cluster->failover_auth_epoch;
}
clusterFailoverReplaceYourMaster();//升级为主节点
}
}
//根据offset的偏差排序,偏差越小可以优先请求被投票
if (server.cluster->mf_end) {//手动failover,可以优先请求被投票
server.cluster->failover_auth_time = mstime();
server.cluster->failover_auth_rank = 0;
}
clusterBroadcastPong(CLUSTER_BROADCAST_LOCAL_SLAVES);
return;
}//还没请求被投票,向整个集群请求被投票
if (server.cluster->failover_auth_sent == 0) {
server.cluster->currentEpoch++;
server.cluster->failover_auth_epoch = server.cluster->currentEpoch;
clusterRequestFailoverAuth();
server.cluster->failover_auth_sent = 1;
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_FSYNC_CONFIG);
return; /* Wait for replies. */
}
//检查自己得到票数是否达到needed_quorum
if (server.cluster->failover_auth_count >= needed_quorum) {
if (myself->configEpoch < server.cluster->failover_auth_epoch) {
myself->configEpoch = server.cluster->failover_auth_epoch;
}
clusterFailoverReplaceYourMaster();//升级为主节点
}
}