Redis源码阅读(五)集群-故障迁移(上)
Redis源码阅读(五)集群-故障迁移(上)
故障迁移是集群非常重要的功能;直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数据,仍能正常提供服务。这里先抛开Redis实际的做法,我们可以自己想下对于Redis集群应该怎么做故障迁移,哪些关键点是必须要实现的。然后再去看Redis源码中具体的实现,是否覆盖了我们想到的关键点,有哪些设计是我们没有想到的,这样看代码的效果会比较好。
我在思考故障迁移这个功能时,首先想到的是节点发生故障时要很快被集群中其他节点发现,尽量缩短集群不可用的时间;其次就是要选出失效节点上的数据可以被迁移到哪个节点上;在选择迁移节点时最好能够考虑节点的负载,避免迁移造成部分节点负载过高。另外,失效节点的数据在其失效前就应该实时的复制到其他节点上,因为一般情况下节点失效有很大概率是机器不可用,如果没有事先执行过数据复制,节点数据就丢失了。最后,就是迁移的执行,除了要将失效节点原有的键值对数据迁移到其他节点上,还要将失效节点原来负责的槽也迁移到其他节点上,而且槽和键值对应该同步迁移,要避免槽被分配到节点A而槽所对应的键值对被分配到节点B的情况。
看过Redis源码后,发现Redis的故障迁移也是以主备复制为基础的,也就是说需要给每个集群主节点配置从节点,这样主节点的数据天然就是实时复制的,在主节点出现故障时,直接在从节点中选择一个接替失效主节点,将该从节点升级为主节点并通知到集群中所有其他节点即可,这样就无需考虑上面提到的第三点和第四点。如果集群中有节点没有配置从节点,那么就不支持故障迁移。
故障检测
1. 节点互发ping消息,将Ping超时的节点置为疑似下线节点
2. 向其他节点共享疑似下线节点
void clusterSendPing(clusterLink *link, int type) { //随机算去本节点所在集群中的任意两个其他node节点(不包括link本节点和link对应的节点)信息发送给link对应的节点
unsigned char buf[sizeof(clusterMsg)];
clusterMsg *hdr = (clusterMsg*) buf;
int gossipcount = , totlen;
/* freshnodes is the number of nodes we can still use to populate the
* gossip section of the ping packet. Basically we start with the nodes
* we have in memory minus two (ourself and the node we are sending the
* message to). Every time we add a node we decrement the counter, so when
* it will drop to <= zero we know there is no more gossip info we can
* send. */
int freshnodes = dictSize(server.cluster->nodes)-; //除去本节点和接收本ping信息的节点外,整个集群中有多少其他节点
// 如果发送的信息是 PING ,那么更新最后一次发送 PING 命令的时间戳
if (link->node && type == CLUSTERMSG_TYPE_PING)
link->node->ping_sent = mstime();
// 将当前节点的信息(比如名字、地址、端口号、负责处理的槽)记录到消息里面
clusterBuildMessageHdr(hdr,type);
/* Populate the gossip fields */
// 从当前节点已知的节点中随机选出两个节点
// 并通过这条消息捎带给目标节点,从而实现 gossip 协议
// 每个节点有 freshnodes 次发送 gossip 信息的机会
// 每次向目标节点发送 3 个被选中节点的 gossip 信息(gossipcount 计数)
while(freshnodes > && gossipcount < ) {
// 从 nodes 字典中随机选出一个节点(被选中节点)
dictEntry *de = dictGetRandomKey(server.cluster->nodes);
clusterNode *this = dictGetVal(de); clusterMsgDataGossip *gossip; ////ping pong meet消息体部分用该结构
int j; if (this == myself ||
this->flags & (REDIS_NODE_HANDSHAKE|REDIS_NODE_NOADDR) ||
(this->link == NULL && this->numslots == ))
{
freshnodes--; /* otherwise we may loop forever. */
continue;
} /* Check if we already added this node */
// 检查被选中节点是否已经在 hdr->data.ping.gossip 数组里面
// 如果是的话说明这个节点之前已经被选中了
// 不要再选中它(否则就会出现重复)
for (j = ; j < gossipcount; j++) { //这里是避免前面随机选择clusterNode的时候重复选择相同的节点
if (memcmp(hdr->data.ping.gossip[j].nodename,this->name,
REDIS_CLUSTER_NAMELEN) == ) break;
}
if (j != gossipcount) continue;
/* Add it */
// 这个被选中节点有效,计数器减一
freshnodes--;
// 指向 gossip 信息结构
gossip = &(hdr->data.ping.gossip[gossipcount]);
// 将被选中节点的名字记录到 gossip 信息
memcpy(gossip->nodename,this->name,REDIS_CLUSTER_NAMELEN);
// 将被选中节点的 PING 命令发送时间戳记录到 gossip 信息
gossip->ping_sent = htonl(this->ping_sent);
// 将被选中节点的 PING 命令回复的时间戳记录到 gossip 信息
gossip->pong_received = htonl(this->pong_received);
// 将被选中节点的 IP 记录到 gossip 信息
memcpy(gossip->ip,this->ip,sizeof(this->ip));
// 将被选中节点的端口号记录到 gossip 信息
gossip->port = htons(this->port);
// 将被选中节点的标识值记录到 gossip 信息
gossip->flags = htons(this->flags);
// 这个被选中节点有效,计数器增一
gossipcount++;
}
// 计算信息长度
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
totlen += (sizeof(clusterMsgDataGossip)*gossipcount);
// 将被选中节点的数量(gossip 信息中包含了多少个节点的信息)
// 记录在 count 属性里面
hdr->count = htons(gossipcount);
// 将信息的长度记录到信息里面
hdr->totlen = htonl(totlen);
// 发送信息
clusterSendMessage(link,buf,totlen);
}
3. 收到集群中超过半数的节点认为某节点处于疑似下线状态,则判定该节点下线,并广播
void clusterSendFail(char *nodename) {
//如果超过一半的主节点认为该nodename节点下线了,则需要把该节点下线信息同步到整个cluster集群
unsigned char buf[sizeof(clusterMsg)];
clusterMsg *hdr = (clusterMsg*) buf;
// 创建下线消息
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
// 记录命令
memcpy(hdr->data.fail.about.nodename,nodename,REDIS_CLUSTER_NAMELEN);
// 广播消息
clusterBroadcastMessage(buf,ntohl(hdr->totlen));
}
void clusterBroadcastMessage(void *buf, size_t len) { //buf里面的内容为clusterMsg+clusterMsgData
dictIterator *di;
dictEntry *de; // 遍历所有已知节点
di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de); // 不向未连接节点发送信息
if (!node->link) continue; // 不向节点自身或者 HANDSHAKE 状态的节点发送信息
if (node->flags & (REDIS_NODE_MYSELF|REDIS_NODE_HANDSHAKE))
continue; // 发送信息
clusterSendMessage(node->link,buf,len);
}
dictReleaseIterator(di);
Redis源码阅读(五)集群-故障迁移(上)的更多相关文章
- dubbo源码解析五 --- 集群容错架构设计与原理分析
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...
- dubbo源码阅读之集群(故障处理策略)
dubbo集群概述 dubbo集群功能的切入点在ReferenceConfig.createProxy方法以及Protocol.refer方法中. 在ReferenceConfig.createPro ...
- tomcat源码阅读之集群
一. 配置: 在tomcat目录下的conf/Server.xml配置文件中增加如下配置: <!-- Cluster(集群,族) 节点,如果你要配置tomcat集群,则需要使用此节点. clas ...
- Redis源码阅读(三)集群-连接初始化
Redis源码阅读(三)集群-连接建立 对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能.3.0之后的Redis支持了集群模式. Redis官方提供 ...
- Redis源码阅读---连接建立
对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能.3.0之后的Redis支持了集群模式. Redis官方提供的集群功能是无中心的,命令请求可以发送到 ...
- Redis源码阅读(六)集群-故障迁移(下)
Redis源码阅读(六)集群-故障迁移(下) 最近私人的事情比较多,没有抽出时间来整理博客.书接上文,上一篇里总结了Redis故障迁移的几个关键点,以及Redis中故障检测的实现.本篇主要介绍集群检测 ...
- Redis源码阅读(四)集群-请求分配
Redis源码阅读(四)集群-请求分配 集群搭建好之后,用户发送的命令请求可以被分配到不同的节点去处理.那Redis对命令请求分配的依据是什么?如果节点数量有变动,命令又是如何重新分配的,重分配的过程 ...
- Redis源码阅读(二)高可用设计——复制
Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...
- Redis源码阅读(一)事件机制
Redis源码阅读(一)事件机制 Redis作为一款NoSQL非关系内存数据库,具有很高的读写性能,且原生支持的数据类型丰富,被广泛的作为缓存.分布式数据库.消息队列等应用.此外Redis还有许多高可 ...
随机推荐
- ubuntu16.04安装visual-studio-code
微软主页的安装说明,https://code.visualstudio.com/docs/setup/linux 有一点英语基础就能看懂,写的很好,一切以官方文档为主 方法一:可以使用umake ...
- 1066. [SCOI2007]蜥蜴【最大流】
Description 在一个r行c列的网格地图中有一些高度不同的石柱,一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃 到边界外. 每行每列中相邻石柱的距离为1,蜥蜴的跳跃距离是d,即蜥蜴可以跳到 ...
- P4906 小奔关闹钟
题目背景 由于今天是星期一,闹钟准时响了,由于小奔太困了,所以她想关停闹钟. 题目描述 可是,他的闹钟电路太复杂了,有很多个开关,每个开关都连着其他开关,其他开关又连着更多的开关,当且仅当所有开关都关 ...
- 最长公共子序列&最长公共子串
首先区别最长公共子串和最长公共子序列 LCS(计算机科学算法:最长公共子序列)_百度百科 最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. 最长公共子序列: http ...
- ES6新特性2:变量的解构赋值
本文摘自ECMAScript6入门,转载请注明出处. ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring).不仅适用于var命令,也适用于let和c ...
- virtualbox+vagrant学习-2(command cli)-6-vagrant init命令
Init——创建Vagrantfile文件 格式: vagrant init [options] [name [url]] 通过创建初始的Vagrantfile文件(如果不存在的话),将当前目录初始化 ...
- 多线程之Thread类
Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知 ...
- Java反射学习四
利用反射调用私有方法.访问私有属性 利用反射,首先是Class对象的获取,之后是Method和Field对象的获取. 以Method为例,从文档中可以看到: getMethod()方法返回的是publ ...
- Linux Shell常用技巧(四)
九. awk实用功能: 和sed一样,awk也是逐行扫描文件的,从第一行到最后一行,寻找匹配特定模板的行,并在这些行上运行“选择”动作.如果一个模板没有指定动作,这些匹配的行就被显示在屏幕上.如果一 ...
- Linux服务-samba
目录 1. samba简介 2. samba访问 Linux服务-samba 1. samba简介 Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件,由服务器及客户端程序构成. 在 ...