redis集群实现(二)集群添加节点

来源:互联网 发布:华为v9网络错误 编辑:程序博客网 时间:2024/05/16 12:07

Redis-3.0.0里,集群添加节点是通过客户端运行cluster meet命令来实现的,命令格式是cluster meet <ip> <port>,如果客户端向A节点发送这条命令,ipport分别是B节点的ipport,就会把ip:port的机器添加进入执行命令的节点所在的集群里。

具体的流程如下:

1.首先客户端向A节点发送cluster meet <ip> <port>命令。

2.A节点在本地为B节点创建对应的数据结构,然后向B节点发送meet命令。

3.B节点在本地为A节点创建相应的数据结构,并向A节点发送PONG消息,表示收到A节点的消息。

4.A节点收到PONG以后,向B节点返回PING消息。

5.A节点通过Gossip协议向集群中的其他节点传播,一段时间以后,集群中所有的节点都会知道B

如图:


理解了原理了以后,我们就来看一看redis-3.0.0里对于cluster meet命令以及后续的过程的代码实现,以便于更加深入的理解redis

redismeet命令的定义是clusterCommand函数,我们看看clusterCommand函数对于meet的实现。

[cpp] view plain copy
  1. void clusterCommand(redisClient *c) {  
  2.     // 不能在非集群模式下使用该命令  
  3.     if (server.cluster_enabled == 0) {   
  4.         addReplyError(c,"This instance has cluster support disabled");  
  5.         return;  
  6.     }      
  7. //匹配命令,如果是meet就进入下边的代码  
  8.     if (!strcasecmp(c->argv[1]->ptr,"meet") && c->argc == 4) {   
  9.         // 将给定地址的节点添加到当前节点所处的集群里面  
  10.   
  11.         long long port;  
  12.   
  13.         // 检查 port 参数的合法性  
  14.         if (getLongLongFromObject(c->argv[3], &port) != REDIS_OK) {  
  15.             addReplyErrorFormat(c,"Invalid TCP port specified: %s",  
  16.                                 (char*)c->argv[3]->ptr);  
  17.             return;  
  18.         }      
  19.   
  20.         // 尝试与给定地址的节点进行连接  
  21.         if (clusterStartHandshake(c->argv[2]->ptr,port) == 0 &&   
  22.             errno == EINVAL)  
  23.         {      
  24.             // 连接失败  
  25.             addReplyErrorFormat(c,"Invalid node address specified: %s:%s",  
  26.                             (char*)c->argv[2]->ptr, (char*)c->argv[3]->ptr);  
  27.         } else {  
  28.             // 连接成功  
  29.             addReply(c,shared.ok);  
  30.         }  
  31.       
  32.     }  
  33. ……………………………………………….  

主要的处理逻辑函数是clusterStartHandshake,这个函数向ip:port进行握手,成功时返回1,我们看看clusterStartHandshake函数的代码实现吧

[cpp] view plain copy
  1. int clusterStartHandshake(char *ip, int port) {  
  2.     clusterNode *n;  
  3.     char norm_ip[REDIS_IP_STR_LEN];  
  4.     struct sockaddr_storage sa;  
  5.   
  6.     // ip 合法性检查  
  7.     if (inet_pton(AF_INET,ip,  
  8.             &(((struct sockaddr_in *)&sa)->sin_addr)))  
  9.     {  
  10.         sa.ss_family = AF_INET;  
  11.     } else if (inet_pton(AF_INET6,ip,  
  12.             &(((struct sockaddr_in6 *)&sa)->sin6_addr)))  
  13.     {  
  14.         sa.ss_family = AF_INET6;  
  15.     } else {  
  16.         errno = EINVAL;  
  17.         return 0;  
  18.     }  
  19.   
  20.     // port 合法性检查  
  21.     if (port <= 0 || port > (65535-REDIS_CLUSTER_PORT_INCR)) {  
  22.         errno = EINVAL;  
  23.         return 0;  
  24.     }  
  25.   
  26.     if (sa.ss_family == AF_INET)  
  27.         inet_ntop(AF_INET,  
  28.             (void*)&(((struct sockaddr_in *)&sa)->sin_addr),  
  29.             norm_ip,REDIS_IP_STR_LEN);  
  30.     else  
  31.         inet_ntop(AF_INET6,  
  32.             (void*)&(((struct sockaddr_in6 *)&sa)->sin6_addr),  
  33.             norm_ip,REDIS_IP_STR_LEN);  
  34.   
  35.     // 检查节点是否已经发送握手请求,如果是的话,那么直接返回,防止出现重复握手  
  36.     if (clusterHandshakeInProgress(norm_ip,port)) {  
  37.         errno = EAGAIN;  
  38.         return 0;  
  39.     }  
  40.   
  41.     // 对给定地址的节点设置一个随机名字  
  42.     // 当 HANDSHAKE 完成时,当前节点会取得给定地址节点的真正名字  
  43.     // 创建一个集群节点,flag设置为MEET,发出meet命令  
  44.     n = createClusterNode(NULL,REDIS_NODE_HANDSHAKE|REDIS_NODE_MEET);  
  45.     memcpy(n->ip,norm_ip,sizeof(n->ip));  
  46.     n->port = port;  
  47.   
  48.     // 将节点添加到集群当中  
  49.     clusterAddNode(n);  
  50.   
  51.     return 1;  
  52. }  

我们继续看createClusterNode函数和clusterAddNode函数。

[cpp] view plain copy
  1. createClusterNode函数创建了一个ClusterNode结构体,并且设置状态为handshake和meet  
  2.  /*  
  3.  * 函数会返回一个被创建的节点,但是并没有把它加入到当前节点的哈希表里 
  4.  */  
  5. clusterNode *createClusterNode(char *nodename, int flags) {  
  6.     clusterNode *node = zmalloc(sizeof(*node));  
  7.   
  8.     // 如果没有指定节点名字,就采用随机的,获取返回信息以后就会设置真正的节点名字  
  9.     if (nodename)  
  10.         memcpy(node->name, nodename, REDIS_CLUSTER_NAMELEN);  
  11.     else  
  12.         getRandomHexChars(node->name, REDIS_CLUSTER_NAMELEN);  
  13.   
  14.     // 初始化属性  
  15.     node->ctime = mstime();  
  16.     node->configEpoch = 0;  
  17.     //这里设置flags为传入的值  
  18.     node->flags = flags;  
  19.     memset(node->slots,0,sizeof(node->slots));  
  20.     node->numslots = 0;  
  21.     node->numslaves = 0;  
  22.     node->slaves = NULL;  
  23.     node->slaveof = NULL;  
  24.     node->ping_sent = node->pong_received = 0;  
  25.     node->fail_time = 0;  
  26.     node->link = NULL;  
  27.     memset(node->ip,0,sizeof(node->ip));  
  28.     node->port = 0;  
  29.     node->fail_reports = listCreate();  
  30.     node->voted_time = 0;  
  31.     node->repl_offset_time = 0;  
  32.     node->repl_offset = 0;  
  33.     listSetFreeMethod(node->fail_reports,zfree);                                                                                                    
  34.   
  35.     return node;  
  36. }  

然后是clusterAddNode函数,这个函数把一个刚刚创建好的节点加入到当前节点哈希表里边,代码非常简单。

[cpp] view plain copy
  1. // 将给定 node 添加到节点表里面  
  2. int clusterAddNode(clusterNode *node) {  
  3.     int retval;  
  4.     // 将 node 添加到当前节点的 nodes表中  
  5.     // 这样接下来当前节点就会创建连向 node的节点  
  6.     retval = dictAdd(server.cluster->nodes,  
  7.             sdsnewlen(node->name,REDIS_CLUSTER_NAMELEN), node);  
  8.     return (retval == DICT_OK) ? REDIS_OK : REDIS_ERR;  
  9. }  

看到这里,你可能会问,怎么没有看到发送MEET信息的代码?其实MEET信息是在serverCron函数里边发送的,serverCron函数是一个周期性执行的函数,一般是每秒调用10次,就是每100ms调用一次。serverCron函数的功能是清除一些过期的key-value和统计信息,复制等一些操作。在serverCron函数里有以下代码:

[cpp] view plain copy
  1. // 如果服务器运行在集群模式下,那么执行集群操作  
  2.     run_with_period(100) {  
  3.         if (server.cluster_enabled) clusterCron();  
  4.     }  

这里就是每100ms会执行clusterCron函数,clusterCron函数执行集群的定期检查工作,在clusterCron函数里执行了发送MEET消息的工作,具体实现如下:

[cpp] view plain copy
  1. void clusterCron(void) {  
  2.     ………………  
  3.     //如果当前节点没有创建连接  
  4.     if (node->link == NULL) {  
  5.     ……………….  
  6.     //如果当前节点有MEET标记就发送MEET消息  
  7.     old_ping_sent = node->ping_sent;  
  8.     clusterSendPing(link, node->flags & REDIS_NODE_MEET ?  
  9.                     CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);  
  10.     ……………………  
  11.     }  

自此,我们就把MEET消息发送出去了~~然后,我们就看看另一个节点是怎么接收到MEET消息的吧。

又回到clusterCron函数,在clusterCron函数里,为没有创建连接的节点结构体设置对应的消息处理函数,代码如下

[cpp] view plain copy
  1. if (node->link == NULL) {  
  2. ……………………  
  3. aeCreateFileEvent(server.el,link->fd,AE_READABLE,  
  4.                     clusterReadHandler,link);  
  5. ……………………  
  6. clusterReadHandler函数调用了clusterProcessPacket函数处理消息包,我们来看clusterProcessPacket函数:  
  7. ………………………………………..  
  8. if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_MEET) {  
  9.         redisLog(REDIS_DEBUG,"Ping packet received: %p", (void*)link->node);  
  10.   
  11.         /*  
  12.          * 如果当前节点是第一次遇见这个节点,并且对方发来的是 MEET信息, 
  13.          * 那么将这个节点添加到集群的节点列表里面。 
  14.          * 节点目前的 flag 、 slaveof等属性的值都是未设置的, 
  15.          * 等当前节点向对方发送 PING 命令之后, 
  16.          * 这些信息可以从对方回复的 PONG信息中取得。 
  17.          */  
  18.         if (!sender && type == CLUSTERMSG_TYPE_MEET) {  
  19.             clusterNode *node;  
  20.   
  21.             // 创建 HANDSHAKE 状态的新节点  
  22.             node = createClusterNode(NULL,REDIS_NODE_HANDSHAKE);  
  23.   
  24.             // 设置 IP 和端口  
  25.             nodeIp2String(node->ip,link);  
  26.             node->port = ntohs(hdr->port);  
  27.   
  28.             // 将新节点添加到集群  
  29.             clusterAddNode(node);  
  30.   
  31.             clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);  
  32.         }  
  33.   
  34.         /* Get info from the gossip section */  
  35.         // 分析并取出消息中的 gossip 节点信息  
  36.         clusterProcessGossipSection(hdr,link);  
  37.   
  38.         /* Anyway reply with a PONG */  
  39.         // 向目标节点返回一个 PONG  
  40.         clusterSendPing(link,CLUSTERMSG_TYPE_PONG);  
  41.     }  
  42. ………………………………………..  

节点发送了PONG,我们就该接收PONG,同样是在clusterProcessPacket函数,接受PONG消息的处理代码如下:

[cpp] view plain copy
  1. if (link->node && type == CLUSTERMSG_TYPE_PONG) {  
  2.   
  3.             // 最后一次接到该节点的 PONG 的时间  
  4.             link->node->pong_received = mstime();  
  5.   
  6.             // 清零最近一次等待 PING 命令的时间  
  7.             link->node->ping_sent = 0;  
  8.   
  9.             /* 接到节点的 PONG 回复,我们可以移除节点的 PFAIL 状态。 
  10.              * 如果节点的状态为 FAIL , 
  11.              * 那么是否撤销该状态要根据 clearNodeFailureIfNeeded()函数来决定。 
  12.              */  
  13.             if (nodeTimedOut(link->node)) {  
  14.                 // 撤销 PFAIL  
  15.                 link->node->flags &= ~REDIS_NODE_PFAIL;  
  16.   
  17.                 clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|  
  18.                                      CLUSTER_TODO_UPDATE_STATE);  
  19.             } else if (nodeFailed(link->node)) {  
  20.                 // 看是否可以撤销 FAIL  
  21.                 clearNodeFailureIfNeeded(link->node);  
  22.             }  
  23.         }  
  24. ………………………………………  

接收到PONG消息以后,撤销了节点的Fail状态,以后就会在ServerCron函数里周期性向新加入节点发送PING,然后新加入的节点返回PONG,这方面的代码会在后续的专题写,自此双方的信息就都了解清楚了。

原创粉丝点击