技术标签: Redis 《Redis 设计与实现》
Redis 集群中的节点分为主节点和从节点,其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求
举个例子,对于包含 7000、7001、7002、7003 四个主节点的集群来说,我们可以将 7004、7005 两个节点添加到集群里面,并将这两个节点设定为节点 7000 的从节点,如图 17-32 所示(图中以双圆形表示主节点,单圆形表示从节点)
表 17-1 记录了集群各个节点的当前状态,以及它们正在做的工作
如果这时,节点 7000 进入下线状态,那么集群中仍在正常运作的几个主节点将在节点 7000 的两个从节点 —— 节点 7004 和节点 7005 中选出一个节点作为新的主节点,这个新的主节点将接管原来节点 7000 负责处理的槽,并继续处理客户端发送的命令请求
例如,如果节点 7004 被选中为新的主节点,那么节点 7004 将接管原来由节点 7000 负责处理的槽 0 至槽 5000,节点 7005 也会从原来的复制节点 7000,改为复制节点 7004,如图 17-33 所示(图中用虚线包围的节点为已下线节点)
表 17-2 记录了在对节点 7000 进行故障转移之后,集群各个节点的当前状态,以及它们正在做的工作
如果在故障转移完成之后,下线的节点 7000 重新上线,那么它键成为节点 7004 的从节点,如图 17-34 所示
表 17-3 展示了节点 7000 复制节点 7004 之后,集群中各个节点的状态
向一个节点发送命令:
CLUSTER REPLICATION <node_id>
可以让接收命令的节点成为 node_id 所指定节点的从节点,并开始对主节点进行复制:
struct clusterNode{
//...
//如果这是一个从节点,那么指向主节点
struct clusterNode *slaveof
//...
};
图 17-35 展示了节点 7004 在复制节点 7000 时的 clusterState 结构:
一个节点成为从节点,并开始复制某个主节点这一信息会通过消息发送给集群中的其他节点,最终集群中的所有节点都会知道某个从节点正在复制某个主节点
集群中的所有节点都会在代表主节点的 clusterNode 结构的 slaves 属性和 numslaves 属性中记录正在复制这个主节点的从节点名单:
struct clusterNode{
//...
//正在复制这个主节点的从节点数量
int numslaves;
//一个数组
//每个数据项指向一个正在复制这个主节点的 clusterNode 结构
struct clusterNode **slaves;
//...
};
举个例子,图 17-36 记录了节点 7004 和节点 7005 成为节点 7000 的从节点之后,集群中的各个节点为节点 7000 创建的 clusterNode 结构的样子:
集群中的每个节点都会定期地向集群中的其他节点发送 PING 消息,以此来检测对方是否在线,如果接收 PING 消息的节点没有在规定的时间内,向发送 PING 消息的节点返回 PONG 消息,那么发送 PING 消息的节点就会将接收 PING 消息的节点标记为疑似下线(probable fail,PFAIL)
举个例子,如果节点 7001 向节点 7000 发送一条 PING 消息,但是节点 7000 没有在规定的时间内,向节点 7001 返回一条 PONG 消息,那么节点 7001 就会在自己的 clusterState.nodes 字典中找到节点 7000 所对应的 clusterNode 结构,并在结构的 flags 属性中打开 REDIS_NODE_PFAIL 标识,以此表示节点 7000 进入了疑似下线状态,如图 17-27 所示
集群中的各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息,例如某个节点是处于在线状态、疑似下线状态(PFAIL),还是已下线状态(FAIL)
当一个主节点 A 通过消息得知主节点 B 认为主节点 C 进入了疑似下线状态时,主节点 A 会在自己的 clusterState.nodes 字典中找到主节点 C 所对应的 clusterNode 结构,并将主节点 B 的下线报告(failure report)添加到 clusterNode 结构的 fail_reports 链表里面:
struct clusterNode{
//...
//一个链表,记录了所有其他节点对该节点的下线报告
list *fail_reports;
//...
};
每个下线报告由一个 clusterNodeFailReport 结构表示:
struct clusterNodeFailReport{
//报告目标节点已经下线的节点
struct clusterNode *node;
//最后一次从 node 节点收到下线报告的时间
//程序使用这个时间戳来检查下线报告是否过期
//(与当前时间相差太久的下线报告会被删除)
mstime_t time;
}typedef clusterNodeFailReport;
举个例子,如果主节点 7001 在收到主节点 7002、主节点 7003 发送的消息后得知,主节点 7002 和主节点 7003 都认为主节点 7000 进入了疑似下线状态,那么主节点 7001 将为主节点 7000 创建图 17-38 所示的下线报告
如果在一个集群里面,半数以上负责处理槽的主节点都将某个主节点 x 报告为疑似下线,那么这个主节点 x 将被标记为已下线(FAIL),将主节点 x 标记为已下线的节点回向集群广播一条关于主节点 x 的 FAIL 消息,所有收到这条 FAIL 消息的节点都会立即将主节点 x 标记为已下线
举个例子,对于图 17-38 所示的下线报告来说,主节点 7002 和主节点 7003 都认为主节点 7000 进入了下线状态,并且主节点 7001 也认为主节点 7000 进入了疑似下线状态(代表主节点 7000 的结构打开了 REDIS_NODE_PFAIL 标识),综合i起来,在集群四个负责处理槽的主节点里面,有三个都将主节点 7000 标记为下线,数量已经超过了半数,所以主节点 7001 会将主节点 7000 标记为已下线,并向集群广播一条关于主节点 7000 的 FAIL 消息
当一个从节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移,以下是故障转移的执行步骤;
新的主节点是通过选举产生的
以下是集群选举新的主节点的方法:
这个选举新主节点的方法和选举领头 Sentinel 的方法非常相似,因为两者都是基于 Raft 算法的领头选举(leader election)方法来实现的
集群中的各个节点通过发送和接收消息(message)来进行通信,我们称发送消息的节点为发送者(sender),接收消息的节点为接收者(receiver)
节点发送的消息主要有以下五种:
一条消息由消息头(header)和消息正文(data)组成,接下来的内容将首先介绍消息头,然后再分别介绍上面提到的五种不同类型的消息正文
节点发送的所有消息都由一个消息头包裹,消息头除了包含消息正文之外,还记录了消息发送者自身的一些信息,因为这些信息也会被消息接收者用到,所以严格来讲,我们可以认为消息头本身也是消息的一部分
每个消息头都由一个 cluster.h/clusterMsg 结构表示:
typedef struct{
//消息的长度(包括这个消息头的长度和消息正文的长度)
uint32_t totlen;
//消息的类型
uint16_t type;
//消息正文包含的节点信息数量
//只在发送 MEET、PING、PONG 这三种 Gossip 协议消息时使用
uint16_t configEpoch;
//发送者的名字(ID)
char sender[REDIS_CLUSTER_NAMELEN];
//发送者目前的槽指派信息
unsigned char myslots[REDIS_CLUSTER_SLOTS/8];
//如果发送者是一个从节点,那么这里记录的是发送者正在复制的主节点的名字
//如果发送者是一个主节点,那么这里记录的是 REDIS_NODE_NULL_NAME
//(一个 40 字节长,值全为 0 的字节数组)
char slaveof[REDIS_CLUSTER_NAMELEN];
//发送者的端口号
uint16_t port;
//发送者的标识值
uint16_t flags;
//发送者所处集群的状态
unsigned char state;
//消息的正文(或者说,内容)
union clusterMsgData data;
}clusterMsg;
clusterMsg.data 属性指向联合 cluster.h/clusterMsgData,这个联合就是消息的正文:
union clusterMsgData{
//MEET、PING、PONG 消息的正文
struct{
//每条 MEET、PING、PONG 消息都包含两个
//clusterMsgDataGossip 结构
clusterMsgDataGossip gossip[1];
}ping;
//FAIL 消息的正文
struct{
clusterMsgDataFail about;
}fail;
//PUBLISH 消息的正文
struct{
clusterMsgDataPublish msg;
}publish;
//其他消息的正文...
};
clusterMsg 结构的 currentEpoch、sender、myslots 等属性记录了发送者自身的节点信息,接收者会根据这些信息,在自己的 clusterState.nodes 字典里找到发送者对应的 clusterNode 结构,并对结构进行更新
举个例子,通过对比接收者为发送者记录的槽指派信息,以及发送者在消息头的 myslots 属性记录的槽指派信息,接收者可以知道发送者的槽指派信息是否发生了变化
又或者说,通过对比接收者为发送者记录的标识值,以及发送者在消息头的 flags 属性记录的标识值,接收者可以知道发送者的状态和角色是否发生了变化,例如节点状态由原来的在线变成了下线,或者由主节点变成了从节点等等
Redis 集群中的各个节点通过 Gossip 协议来交换各自关于不同节点的状态信息,其中 Gossip 协议由 MEET、PING、PONG 三种消息实现,这三种消息的正文都由两个 cluster.h/clusterMsgDataGossip 结构组成:
union clusterMsgData{
//...
//MEET、PING 和 PONG 消息的正文
struct{
//每条 MEET、PING、PONG 消息都包含两个
//clusterMsgDataGossip 结构
clusterMsgDataGossip gossip[1];
}ping;
//其他消息的正文...
};
因为 MEET、PING、PONG 三种消息都使用相同的消息正文,所以节点通过消息头的 type 属性来判断一条消息是 MEET 消息、PING 消息还是 PONG 消息
每次发送 MEET、PING、PONG 消息时,发送者都从自己的已知节点列表中随机选出两个节点(可以是主节点或者从节点),并将这两个被选中节点的信息分别保存到两个 clusterMsgDataGossip 结构里面
clusterMsgDataGossip 结构记录了被选中节点的名字,发送者与被选中节点最后一次发送和接收 PING 消息和 PONG 消息的时间戳,被选中节点的 IP 地址和端口号,以及被选中节点的标识值:
typedef struct{
//节点的名字
char nodename[REDIS_CLUSTER_NAMELEN];
//最后一次向该节点发送 PING 消息的时间戳
uint32_t pong_received;
//节点的 IP 地址
char ip[16];
//节点的端口号
uint16_t port;
//节点的标识值
uint16_t flags;
}clusterMsgDataGossip;
当接收者收到 MEET、PING、PONG 消息时,接收者会访问消息正文中的两个 clusterMsgDataGossip 结构,并根据自己是否认实 clusterMsgDataGossip 结构中记录的被选中节点来选择进行哪种操作:
举个发送 PING 消息和返回 PONG 消息的例子,假设在一个包含 A、B、C、D、E、F 六个节点的集群里:
整个通信过程如图 17-41 所示
当集群里的主节点 A 将主节点 B 标记为已下线(FAIL)时,主节点 A 将向集群广播一条关于主节点 B 的 FAIL 消息,所有接收到这条 FAIL 消息的节点都会将主节点 B 标记为已下线
在集群的节点数量比较大的情况下,单纯使用 Gossip 协议来传播节点的已下线信息会给节点的信息更新带来一定延迟,因为 Gossip 协议消息通常需要一段时间才能传播至整个集群,而发送 FAIL 消息可以让集群里的所有节点立即知道某个主节点已下线,从而尽快判断是否需要将集群标记为下线,又或者对下线主节点进行故障转移
FAIL 消息的正文由 cluster.h/clusterMsgDataFail 结构表示,这个结构只包含一个 nodename 属性,该属性记录了已下线节点的名字:
typedef struct{
char nodename[REDIS_CLUSTER_NAMELEN];
}clusterMsgDataFail;
因为集群里的所有节点都一个独一无二的名字,所以 FAIL 消息里面只需要保存下线节点的名字,接收到消息的节点就可以根据这个名字来判断是哪个节点下线了
举个例子,对于包含 7000、7001、7002、7003 四个主节点的集群来说;
图 17-42 至图 17-44 展示了节点发送和接收 FAIL 消息的整个过程
当客户端向集群中的某个节点发送命令:
PUBLISH <channel> <message>
的时候,接收到 PUBLISH 命令的节点不仅会向 channel 频道发送消息 message,它还会向集群广播一条 PUBLISH 消息,所有接收到这条 PUBLISH 消息的节点都会向 channel 频道发送 message 消息
换句话说,向集群中的某个节点发送命令;
PUBLISH <channel> <message>
将导致集群中的所有节点都向 channel 频道发送 message 消息
举个例子,对于包含 7000、7001、7002、7003 四个节点的集群来说,如果节点 7000 收到了客户端发送的 PUBLISH 命令,那么节点 7000 将向 7001、7002、7003 三个节点发送 PUBLISH 消息,如图 17-45 所示
PUBLISH 消息的正文由 cluster.h/clusterMsgDataPublish 结构表示;
typedef struct{
uint32_t channel_len;
uint32_t message_len;
//定义为 8 字节只是为了对齐其他消息结构
//实际的长度由保存的内容决定
unsigned char bulk_data[8];
}clusterMsgDataPublish;
clusterMsgDataPublish 结构的 bulk_data 属性是一个字节数组,这个字节数组保存了客户端通过 PUBLISH 命令发送给节点的 channel 参数和 message 参数,而结构的 channel_len 和 message_len 则分别保存了 channel 参数的长度和 message 参数的长度:
举个例子,如果节点收到的 PUBLISH 命令为:
PUBLISH "news.it" "hello"
那么节点发送的 PUBLISH 消息的 clusterMsgDataPublish 结构将如图 17-46 所示:
其中 bulk_data 数组的前七个字节保存了 channel 参数的值 “news.it”,而 bulk_data 数组的后五个字节则保存了 message 参数的值 “hello”
为什么不直接向所有节点广播 PUBLISH 命令
实际上,要让集群的所有节点都执行相同的 PUBLISH 命令,最简单的方法就是向所有节点广播相同的 PUBLISH 命令,这也是 Redis 在复制 PUBLISH 命令时所使用的方法,不过因为这种做法并不符合 Redis 集群的 “各个节点通过发送和接收消息来进行通信” 这一规则,所以节点没有采取广播 PUBLISH 命令的做法
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf