redis集群实现(二)集群添加节点
来源:互联网 发布:c语言中[]是什么意思 编辑:程序博客网 时间:2024/06/03 02:36
在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; } //匹配命令,如果是meet就进入下边的代码 if (!strcasecmp(c->argv[1]->ptr,"meet") && c->argc == 4) { // 将给定地址的节点添加到当前节点所处的集群里面 long long port; // 检查 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; // ip 合法性检查 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; } // port 合法性检查 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; } // 对给定地址的节点设置一个随机名字 // 当 HANDSHAKE 完成时,当前节点会取得给定地址节点的真正名字 // 创建一个集群节点,flag设置为MEET,发出meet命令 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; //这里设置flags为传入的值 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函数,这个函数把一个刚刚创建好的节点加入到当前节点哈希表里边,代码非常简单。
// 将给定 node 添加到节点表里面int clusterAddNode(clusterNode *node) { int retval; // 将 node 添加到当前节点的 nodes表中 // 这样接下来当前节点就会创建连向 node的节点 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) { ………………. //如果当前节点有MEET标记就发送MEET消息 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); /* * 如果当前节点是第一次遇见这个节点,并且对方发来的是 MEET信息, * 那么将这个节点添加到集群的节点列表里面。 * 节点目前的 flag 、 slaveof等属性的值都是未设置的, * 等当前节点向对方发送 PING 命令之后, * 这些信息可以从对方回复的 PONG信息中取得。 */ if (!sender && type == CLUSTERMSG_TYPE_MEET) { clusterNode *node; // 创建 HANDSHAKE 状态的新节点 node = createClusterNode(NULL,REDIS_NODE_HANDSHAKE); // 设置 IP 和端口 nodeIp2String(node->ip,link); node->port = ntohs(hdr->port); // 将新节点添加到集群 clusterAddNode(node); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); } /* Get info from the gossip section */ // 分析并取出消息中的 gossip 节点信息 clusterProcessGossipSection(hdr,link); /* Anyway reply with a PONG */ // 向目标节点返回一个 PONG clusterSendPing(link,CLUSTERMSG_TYPE_PONG); }………………………………………..
节点发送了PONG,我们就该接收PONG,同样是在clusterProcessPacket函数,接受PONG消息的处理代码如下:
if (link->node && type == CLUSTERMSG_TYPE_PONG) { // 最后一次接到该节点的 PONG 的时间 link->node->pong_received = mstime(); // 清零最近一次等待 PING 命令的时间 link->node->ping_sent = 0; /* 接到节点的 PONG 回复,我们可以移除节点的 PFAIL 状态。 * 如果节点的状态为 FAIL , * 那么是否撤销该状态要根据 clearNodeFailureIfNeeded()函数来决定。 */ if (nodeTimedOut(link->node)) { // 撤销 PFAIL link->node->flags &= ~REDIS_NODE_PFAIL; clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } else if (nodeFailed(link->node)) { // 看是否可以撤销 FAIL clearNodeFailureIfNeeded(link->node); } }………………………………………
接收到PONG消息以后,撤销了节点的Fail状态,以后就会在ServerCron函数里周期性向新加入节点发送PING,然后新加入的节点返回PONG,这方面的代码会在后续的专题写,自此双方的信息就都了解清楚了。
- redis集群实现(二)集群添加节点
- redis集群实现(二)集群添加节点
- Redis集群添加节点
- redis集群之添加节点
- hadoop2.6.0添加集群节点(二)
- redis集群实现(三)集群删除节点
- redis集群实现(三)集群删除节点
- redis-cluster集群添加节点、删除节点
- redis的集群,添加和删除节点
- redis的集群搭建与添加节点
- Redis集群动态添加和删除节点
- Redis集群进阶-添加新节点到集群
- Redis新集群操作(在已有的集群上添加新节点)
- Redis Sentinel实现集群节点故障转移
- redis集群动态添加主节点和从节点
- redis集群动态添加主节点和从节点
- Redis集群增删节点
- redis集群删除节点
- 蓝桥杯 金属采集 树形动态规划
- poj3468 zkw线段树
- Class.forName()用法详解
- 【二分】【挖金矿】
- VisualSVN-Server的安装以及简单使用
- redis集群实现(二)集群添加节点
- 顺序栈的进栈、出栈操作
- 基于Libsvm的图像分类
- 文章标题
- top
- hadoop学习之HDFS(2.5):windows下eclipse远程连接linux下的hadoop集群并测试wordcount例子
- u-boot-2016.09移植(2)-uboot启动简易分析
- 计算三维空间两点距离【马士兵面向对象课后习题1】
- pandorabox安装迅雷远程下载插件