在Redis-3.0.0里,集群添加节点是通过客户端运行cluster meet命令来实现的,命令格式是cluster meet <ip> <port>,如果客户端向A节点发送这条命令,ip和port分别是B节点的ip和port,就会把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。
redis的meet命令的定义是clusterCommand函数,我们看看clusterCommand函数对于meet的实现。
- void clusterCommand(redisClient *c) {
-
- if (server.cluster_enabled == 0) {
- addReplyError(c,"This instance has cluster support disabled");
- return;
- }
-
- if (!strcasecmp(c->argv[1]->ptr,"meet") && c->argc == 4) {
-
-
- long long port;
-
-
- if (getLongLongFromObject(c->argv[3], &port) != REDIS_OK) {
- addReplyErrorFormat(c,"Invalid TCP port specified: %s",
- (char*)c->argv[3]->ptr);
- return;
- }
-
-
- if (clusterStartHandshake(c->argv[2]->ptr,port) == 0 &&
- errno == EINVAL)
- {
-
- addReplyErrorFormat(c,"Invalid node address specified: %s:%s",
- (char*)c->argv[2]->ptr, (char*)c->argv[3]->ptr);
- } else {
-
- addReply(c,shared.ok);
- }
-
- }
- ……………………………………………….
主要的处理逻辑函数是clusterStartHandshake,这个函数向ip:port进行握手,成功时返回1,我们看看clusterStartHandshake函数的代码实现吧
- int clusterStartHandshake(char *ip, int port) {
- clusterNode *n;
- char norm_ip[REDIS_IP_STR_LEN];
- struct sockaddr_storage sa;
-
-
- if (inet_pton(AF_INET,ip,
- &(((struct sockaddr_in *)&sa)->sin_addr)))
- {
- sa.ss_family = AF_INET;
- } else if (inet_pton(AF_INET6,ip,
- &(((struct sockaddr_in6 *)&sa)->sin6_addr)))
- {
- sa.ss_family = AF_INET6;
- } else {
- errno = EINVAL;
- return 0;
- }
-
-
- if (port <= 0 || port > (65535-REDIS_CLUSTER_PORT_INCR)) {
- errno = EINVAL;
- return 0;
- }
-
- if (sa.ss_family == AF_INET)
- inet_ntop(AF_INET,
- (void*)&(((struct sockaddr_in *)&sa)->sin_addr),
- norm_ip,REDIS_IP_STR_LEN);
- else
- inet_ntop(AF_INET6,
- (void*)&(((struct sockaddr_in6 *)&sa)->sin6_addr),
- norm_ip,REDIS_IP_STR_LEN);
-
-
- if (clusterHandshakeInProgress(norm_ip,port)) {
- errno = EAGAIN;
- return 0;
- }
-
-
-
-
- n = createClusterNode(NULL,REDIS_NODE_HANDSHAKE|REDIS_NODE_MEET);
- memcpy(n->ip,norm_ip,sizeof(n->ip));
- n->port = port;
-
-
- clusterAddNode(n);
-
- return 1;
- }
我们继续看createClusterNode函数和clusterAddNode函数。
- createClusterNode函数创建了一个ClusterNode结构体,并且设置状态为handshake和meet
-
-
-
- clusterNode *createClusterNode(char *nodename, int flags) {
- clusterNode *node = zmalloc(sizeof(*node));
-
-
- if (nodename)
- memcpy(node->name, nodename, REDIS_CLUSTER_NAMELEN);
- else
- getRandomHexChars(node->name, REDIS_CLUSTER_NAMELEN);
-
-
- node->ctime = mstime();
- node->configEpoch = 0;
-
- node->flags = flags;
- memset(node->slots,0,sizeof(node->slots));
- node->numslots = 0;
- node->numslaves = 0;
- node->slaves = NULL;
- node->slaveof = NULL;
- node->ping_sent = node->pong_received = 0;
- node->fail_time = 0;
- node->link = NULL;
- memset(node->ip,0,sizeof(node->ip));
- node->port = 0;
- node->fail_reports = listCreate();
- node->voted_time = 0;
- node->repl_offset_time = 0;
- node->repl_offset = 0;
- listSetFreeMethod(node->fail_reports,zfree);
-
- return node;
- }
然后是clusterAddNode函数,这个函数把一个刚刚创建好的节点加入到当前节点哈希表里边,代码非常简单。
-
- int clusterAddNode(clusterNode *node) {
- int retval;
-
-
- retval = dictAdd(server.cluster->nodes,
- sdsnewlen(node->name,REDIS_CLUSTER_NAMELEN), node);
- return (retval == DICT_OK) ? REDIS_OK : REDIS_ERR;
- }
看到这里,你可能会问,怎么没有看到发送MEET信息的代码?其实MEET信息是在serverCron函数里边发送的,serverCron函数是一个周期性执行的函数,一般是每秒调用10次,就是每100ms调用一次。serverCron函数的功能是清除一些过期的key-value和统计信息,复制等一些操作。在serverCron函数里有以下代码:
-
- run_with_period(100) {
- if (server.cluster_enabled) clusterCron();
- }
这里就是每100ms会执行clusterCron函数,clusterCron函数执行集群的定期检查工作,在clusterCron函数里执行了发送MEET消息的工作,具体实现如下:
- void clusterCron(void) {
- ………………
-
- if (node->link == NULL) {
- ……………….
-
- old_ping_sent = node->ping_sent;
- clusterSendPing(link, node->flags & REDIS_NODE_MEET ?
- CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
- ……………………
- }
自此,我们就把MEET消息发送出去了~~然后,我们就看看另一个节点是怎么接收到MEET消息的吧。
又回到clusterCron函数,在clusterCron函数里,为没有创建连接的节点结构体设置对应的消息处理函数,代码如下
- if (node->link == NULL) {
- ……………………
- aeCreateFileEvent(server.el,link->fd,AE_READABLE,
- clusterReadHandler,link);
- ……………………
- clusterReadHandler函数调用了clusterProcessPacket函数处理消息包,我们来看clusterProcessPacket函数:
- ………………………………………..
- if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_MEET) {
- redisLog(REDIS_DEBUG,"Ping packet received: %p", (void*)link->node);
-
-
-
-
-
-
-
-
- if (!sender && type == CLUSTERMSG_TYPE_MEET) {
- clusterNode *node;
-
-
- node = createClusterNode(NULL,REDIS_NODE_HANDSHAKE);
-
-
- nodeIp2String(node->ip,link);
- node->port = ntohs(hdr->port);
-
-
- clusterAddNode(node);
-
- clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
- }
-
-
-
- clusterProcessGossipSection(hdr,link);
-
-
-
- clusterSendPing(link,CLUSTERMSG_TYPE_PONG);
- }
- ………………………………………..
节点发送了PONG,我们就该接收PONG,同样是在clusterProcessPacket函数,接受PONG消息的处理代码如下:
- if (link->node && type == CLUSTERMSG_TYPE_PONG) {
-
-
- link->node->pong_received = mstime();
-
-
- link->node->ping_sent = 0;
-
-
-
-
-
- if (nodeTimedOut(link->node)) {
-
- link->node->flags &= ~REDIS_NODE_PFAIL;
-
- clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
- CLUSTER_TODO_UPDATE_STATE);
- } else if (nodeFailed(link->node)) {
-
- clearNodeFailureIfNeeded(link->node);
- }
- }
- ………………………………………
接收到PONG消息以后,撤销了节点的Fail状态,以后就会在ServerCron函数里周期性向新加入节点发送PING,然后新加入的节点返回PONG,这方面的代码会在后续的专题写,自此双方的信息就都了解清楚了。