Redis高可用之集群架构(第三部分)

news2025/1/12 12:21:42

引言

在这里插入图片描述
集群的实际环境模拟可以参考我之前的文章 单机模拟集群(三主两从)

一、集群的工作原理

集群中的节点只能使用0号数据库,而单机数据库没有这个限制。集群中的节点本质上就是一个运行在集群模式下的Redis服务器,Redis服务器在启动的时候会根据redis.conf配置文件中的cluster-enabled 是否为yes 来决定是否开启服务器的集群模式。

1.1 槽指派

Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于这16384个槽中的一个,集群中的每个节点可以处理0~16384个槽。

当数据库中的16384个槽都有节点在处理时,集群处于上线状态;相反的,如果有任何一个槽没有得到处理,那么集群处于下线状态。

每当往数据库中添加新的键值对时,Redis都会对其key计算CRC16的值,再对16384取余,计算出槽位。如果是对节点数量进行取余,在发生动态扩缩容时就会发生错乱,所以,Redis采用了固定算法: CRC16(key)% 16384。

使用CLUSTER KEYSLOT key 命令,可以查看key属于哪个槽位。

127.0.0.1:7000> CLUSTER KEYSLOT name
2022

每个节点都会在自己的clusterNode.slots 数组中保存自己处理的槽信息,这个信息会通过消息的方式发送给集群中的其他节点。如果想知道某个槽i是否已经被指派,或者指派给了哪个节点,程序需要遍历clusterState.nodes字典中的所有clusterNode,检查他们的slots数组,直到找到处理槽i的节点位置,时间复杂度为O(n),n为nodes字典保存的节点数量。

为了优化这个查询,Redis在clusterState的slots数组中,为每个槽i(0 <= i <= 16383)保存处理自身的clusterNode节点信息,如果没有则为NULL,这是如果程序想知道槽i被谁处理的时间复杂度就是O(1)。

在这里插入图片描述

1.2 如何判断某个槽是不是当前节点处理

当我们在客户端的命令行输入命令时,redis会先计算该键的槽位,当计算出键所属的槽位i之后,节点就会检查自己 在clusterState.slots中的数组中的第i项,如果指向自己,说明该槽位i是由当前节点负责,可以执行客户端发送的命令;如果clusterState.slots[i] 不等于 clusterState.myself,说明槽位i是由其他节点负责,当前节点会根据clusterState.slots[i] 的指向找到对应的clusterNode结构所记录的ip和port,向客户端返回MOVED错误,指引客户端转向正确的节点。

1.3 MOVE错误

当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED错误,指引客户端转向正在负责槽的节点。

MOVED slot ip:port

1.4 重新分片

Redis集群的重新分片操作主要用来动态扩缩容。重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽修改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点迁移到目标节点。

重新分片操作可以在线进行,在重新分片的过程中,集群不需要下线。源节点可目标节点都能处理命令请求。

1.5 ASK错误

在进行重新分片期间,槽位中的数据从源节点向目的节点迁移的过程中,可能会出现一部分数据在源节点中,一部分数据在目标节点。当客户端向源节点发送一个与数据库键有关的命令,并且要处理的数据库键属于正在被迁移的槽位时:

  1. 源节点会先在自己的数据库中找,如果找到就执行客户端的命令
  2. 如果没找到,源节点会检查自己的clusterState.migrating_slots_to[i],找到正在迁移的目标节点, 向客户端返回一个ASK错误,指引客户端转向正在迁移的目标节点,并再次发送要执行的命令。

ASK错误和MOVED错误的区别:
相同点:都会导致客户端转向;
不同点:MOVED错误表示槽i的复制权已经转移到另一个节点了,而ASK错误只是两个节点在迁移槽的过程中使用的临时措施。

1.6 复制与故障转移

设置从节点,让当前节点去复制node节点:

CLUSTER REPLICATE <node_id>

收到该命令的节点会执行以下步骤:

  1. 在自己的clusterState.nodes字典中找到node_id对应的clusterNode结构,并将自己的clusterState.myself.slaveof指针指向要复制的目标节点
  2. 修改自己clusterState.myself.flags 标识,打开 REDIS_NODE_SLVAE标识,表示这个节点已经由原来的主节点变成了从节点
  3. 最后,节点会调用复制代码,根据 clusterState.myself.slaveof 指向的 clusterNode 结构中的所保存的ip和port,对主节点进行复制

一个节点成为从节点,并开始复制某个主节点这一信息会通过消息的方式发送给集群中的其他节点,最终集群中的所有节点都会知道该节点正在复制某个主节点。

比如,现在要给7000主服务添加两个从服务器,分别为节点:7004 和 7005。节点 7004 和 7005 各自clusterState结构和clusterNode结构,如下图:
在这里插入图片描述
7004 和 7005 称为节点7000 从节点后,集群中的各个节点为节点7000创建的clusterNode结构的样子。

在这里插入图片描述

1.7 故障检测

集群中的每个节点都会定期向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果收到PING的节点没有在规定的时间内回复PONG消息,那么发送PING消息的节点就会将该节点标记为疑似下线。具体就是在自己的clusterState.nodes字典中,找到疑似下线的clusterNode结构,把结构中flags属性打开REDIS_NODE_PFAIL标识,以此表示目标节点进入疑似下线。

1.8 故障转移

当一个节点发现自己正在复制的主节点进入了已下线状态,从节点将开始对已下线的主节点进行故障转移,故障转移步骤如下:

  1. 从复制下线主节点的从节点中选一个出来,执行SLAVEOF no one 命令,成为新的主节点
  2. 新的主节点会撤销所有对已下线主节点的槽指派,将这些槽指派全部指派给自己
  3. 新的主节点向集群广播一条PONG消息,告诉集群中其他节点,我是新的主节点并且已经接管了原来的槽位
  4. 新的主节点开始出来与自己负责处理的槽位相关的命令请求,故障转移完成。

二、集群创建的两种方式

1.1 手动创建集群

# 节点会面
cluster meet ip port
# 分配槽位
cluster addslots slot
# 分配主从
cluster replicate node-id

举例:创建一个集群,包含三个节点,分别为: 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 ,让节点7000去认识7001和7002

127.0.0.1:7000> CLUSTER MEET 127.0.0.1  7001
OK

127.0.0.1:7000> CLUSTER MEET 127.0.0.1  7002
OK
127.0.0.1:7000> CLUSTER ADDSLOTS 0 1 2 3 ...  5000
OK

为 7001 节点添加槽位 5001 ~ 10000

127.0.0.1:7001> CLUSTER ADDSLOTS 5001 5002 ... 10000
OK

为 7002 节点添加槽位 10001 ~ 16383

127.0.0.1:7002> CLUSTER ADDSLOTS 10001 10002 ... 16383
OK

1.2 智能创建集群

redis-cli --cluster create host1:port1 ... hostN:portN --cluster-replicas <arg>

上面的命令表示,创建一个包含N个节点的集群,每个主服务器的从服务器的数量为 arg。

三、CLUSTER MEET命令实现

假设我们在A客户端的终端中输入CLUSTER <B_ip> <B_port>,A在收到命令后会与节点B进行握手,握手流程如下:

  • 节点A会为节点B创建clusterNode结构,并将该结构加入到自己的clusterState.nodes字典里,节点A根据给定的B_ip和B_port 向节点B发送一条MEET消息
  • 节点B收到A的MEET消息,为节点A创建clusterNode结构,并将该结构加入到自己的clusterState.nodes字典里,为节点A回复一条PONG消息
  • 节点A正常收到节点B的PONG,就知道节点B已经成功接收了自己的MEET消息,再向节点B发送一条PING消息
  • 节点B正常收到节点A的PING消息,就知道A成功收到了自己的PONG,握手完成。
  • 节点A会将节点B的消息通过Gossip协议传播给集群中的其他节点,让其他节点也与B握手,最终一段时间后,节点B就会被集群中的所有节点认识。

三个节点的flags都是REDIS_CLUSTER_MASTER,说明三个节点都是主节点。节点7001 和 7002 也会创建类似下面的节点,不同的是myself的指向。
在这里插入图片描述

四、源码分析

clusterNode 这个结构保存集群中的一个节点的状态,节点的名字、创建时间、节点的ip和port等,定义如下:

typedef struct clusterNode {
    // 创建节点的时间
    mstime_t ctime; /* Node object creation time. */
    // 节点的名字,40位十六进制字符组成
    char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
    // 节点标识(主、从节点 以及 节点的状态 在线还是下线)
    int flags;      /* CLUSTER_NODE_... */
    // 当前配置纪元,用于故障转移
    uint64_t configEpoch; /* Last configEpoch observed for this node */

    // 节点处理的槽位
    // 是个二进制数组,长度为2048个字节,共包含 16384个二进制位
    // 索引范围:0~16383 通过查看对应bit位是否为1  如果索引i对应的bit为1,表示节点处理槽i
    unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
    sds slots_info; /* Slots info represented by string. */

    // 节点处理的槽位的个数,也就是slots数组中1的个数
    int numslots;   /* Number of slots handled by this node */
    // 如果是主节点,从节点的个数
    int numslaves;  /* Number of slave nodes, if this is a master */
    // 从节点的集合
    struct clusterNode **slaves; /* pointers to slave nodes */
    // 如果是从服务器,指向复制的主服务器
    struct clusterNode *slaveof; /* pointer to the master node. Note that it
                                    may be NULL even if the node is a slave
                                    if we don't have the master node in our
                                    tables. */
    // 最后一次发送ping的时间
    mstime_t ping_sent;      /* Unix time we sent latest ping */
    mstime_t pong_received;  /* Unix time we received the pong */
    mstime_t data_received;  /* Unix time we received any data */
    mstime_t fail_time;      /* Unix time when FAIL flag was set */
    mstime_t voted_time;     /* Last time we voted for a slave of this master */
    mstime_t repl_offset_time;  /* Unix time we received offset for this node */
    mstime_t orphaned_time;     /* Starting time of orphaned master condition */

    // 节点的复制偏移量
    long long repl_offset;      /* Last known repl offset for this node. */
    // 节点的IP地址和port
    char ip[NET_IP_STR_LEN];  /* Latest known IP address of this node */
    int port;                   /* Latest known clients port (TLS or plain). */
    int pport;                  /* Latest known clients plaintext port. Only used
                                   if the main clients port is for TLS. */
    int cport;                  /* Latest known cluster port of this node. */
    // 保存连接节点需要的相关信息
    clusterLink *link;          /* TCP/IP link with this node */
     // 故障检测中使用,记录了其他所有的节点,对该节点的下线报告
    list *fail_reports;         /* List of nodes signaling this as failing */
} clusterNode;

clusterLink 该结构保存了连接节点所需要的信息,如连接对象,收发缓冲区等,定义如下:

/* clusterLink encapsulates everything needed to talk with a remote node. */
typedef struct clusterLink {
    // 连接创建时间
    mstime_t ctime;             /* Link creation time */
    // 连接对象
    connection *conn;           /* Connection to remote node */
    // 发送缓冲区
    sds sndbuf;                 /* Packet send buffer */
    // 接收缓冲区
    char *rcvbuf;               /* Packet reception buffer */
    // 接收缓冲区使用的大小
    size_t rcvbuf_len;          /* Used size of rcvbuf */
    // 接收缓冲区实际分配的大小
    size_t rcvbuf_alloc;        /* Allocated size of rcvbuf */
    // 与这个节点相关联的节点,没有就为NULL
    struct clusterNode *node;   /* Node related to this link if any, or NULL */
} clusterLink;

clusterState,这个结构记录了在当前节点的视角下,集群目前的状态,如:上线还是下线,有多少个节点,集群当前的配置纪元是多少等,定义如下:

typedef struct clusterState {
    // 指向当前节点的指针
    clusterNode *myself;  /* This node */
    // 集群当前的配置纪元,用于实现故障转移
    uint64_t currentEpoch;
    // 集群当前状态  在线还是下线
    int state;            /* CLUSTER_OK, CLUSTER_FAIL, ... */
    // 至少处理一个槽位的主节点的数量
    int size;             /* Num of master nodes with at least one slot */

    // 集群节点名单,包含所有的节点  name --> clusterNode
    dict *nodes;          /* Hash table of name -> clusterNode structures */
    
    dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
    // 用来实现重新分片
    // 记录当前节点正在迁移到其他节点的槽
    clusterNode *migrating_slots_to[CLUSTER_SLOTS];
    // 记录当前节点正在从其他节点导入的槽
    // 假设importing_slots_from[i]的值不为NULL,指向clusterNode结构,表示当前节点正在从clusterNode指向的节点导入槽i
    clusterNode *importing_slots_from[CLUSTER_SLOTS];

    // slots包含16384个项,每个项都是一个clusterNode的指针
    // 如果slots[i] 指向一个clusterNode结构,说明槽i已经指派给clusterNode表示的节点了
    // 如果为NULL,说明还没指派给任何节点
    clusterNode *slots[CLUSTER_SLOTS];

    uint64_t slots_keys_count[CLUSTER_SLOTS];
    rax *slots_to_keys;
    /* The following fields are used to take the slave state on elections. */
    mstime_t failover_auth_time; /* Time of previous or next election. */
    int failover_auth_count;    /* Number of votes received so far. */
    int failover_auth_sent;     /* True if we already asked for votes. */
    int failover_auth_rank;     /* This slave rank for current auth request. */
    uint64_t failover_auth_epoch; /* Epoch of the current election. */
    int cant_failover_reason;   /* Why a slave is currently not able to
                                   failover. See the CANT_FAILOVER_* macros. */
    /* Manual failover state in common. */
    mstime_t mf_end;            /* Manual failover time limit (ms unixtime).
                                   It is zero if there is no MF in progress. */
    /* Manual failover state of master. */
    clusterNode *mf_slave;      /* Slave performing the manual failover. */
    /* Manual failover state of slave. */
    long long mf_master_offset; /* Master offset the slave needs to start MF
                                   or -1 if still not received. */
    int mf_can_start;           /* If non-zero signal that the manual failover
                                   can start requesting masters vote. */
    // 最后一次投票的纪元
    /* The following fields are used by masters to take state on elections. */
    uint64_t lastVoteEpoch;     /* Epoch of the last vote granted. */
    int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
    /* Messages received and sent by type. */
    long long stats_bus_messages_sent[CLUSTERMSG_TYPE_COUNT];
    long long stats_bus_messages_received[CLUSTERMSG_TYPE_COUNT];
    // 疑似下线节点的个数
    long long stats_pfail_nodes;    /* Number of nodes in PFAIL status,
                                       excluding nodes without address. */
} clusterState;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/118021.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【endnote学习】为什么引用文献时期刊名没有显示为缩写名形式

为什么引用文献时期刊名没有显示为缩写名形式问题描述问题解决问题描述 在引用文献时&#xff0c;发现有个别文献引用信息中期刊名没有显示为缩写形式。比如(选择显示格式为AIChE): 引用信息里&#xff0c;期刊名“Physical review B”没有自动显示为缩写名。 出现这种情况有…

c++算法基础必刷题目——前缀和与差分

文章目录前缀和与差分算法&#xff1a;1、校门外的树2、值周3、中位数图4、激光炸弹5、二分6、货仓选址前缀和与差分算法&#xff1a; 前缀和与差分算法主要是为了快速求出某个区间的和&#xff0c;例如有一个数组a[10]{0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4…

unity编辑器窗口介绍

Hierarchy 摆放了unity游戏中使用的节点。 Scene 场景编辑视图&#xff0c;经常用到。 栅格 场景编辑视图中&#xff0c;有一些栅格&#xff0c;用下面这个就可以控制是否展示栅格。 天空盒&#xff08;skybox&#xff09; 天空一片蓝色&#xff0c;也是因为初始创建了蓝色的…

【聆思CSK6 视觉AI开发套件试用】AI Demo试用

本篇文章来自极术社区与聆思科技组织的CSK6 视觉AI开发套件活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;kings669669 AI套件外观 环境搭建 按照官网手册&#xff0c;我在Windows环境下遇到一些问题&#xff0c;在这里给出我的一些解决办法。不知道…

端到端数据战略,亚马逊云科技为数据服务带来了什么?

大约十年前&#xff0c;维克托舍恩伯格在《大数据时代》一书中直言&#xff1a;世界的本质是数据&#xff0c;大数据将开启一次重大的时代转型。 十年之后&#xff0c;维克托舍恩伯格的预言逐渐成真。全球数字经济近年来的蓬勃发展&#xff0c;推动了各行各业的加速转型。如今…

生成对抗:少样本学习

GAN:少样本学习 任何深度学习模型要获得较好结果往往需要大量的训练数据。但是&#xff0c;高质量的数据往往是稀缺的和昂贵的。好消息是&#xff0c;自从GANs问世以来&#xff0c;这个问题得到妥善解决&#xff0c;我们可以通过GAN来生成高质量的合成数据样本帮助模型训练。通…

vue 使用 PDF.js 浏览pdf文件

学习关键语句: 使用 PDF.js 在网页浏览pdf vue 使用 PDF.js vue 浏览pdf文件 写在前面 很头大 , 本来网络实际地址的 pdf 文件直接放在 iframe 的 src 中就可以浏览 pdf 文件的 , 但是对于虚拟地址来说 , 这样子只会让网页当场开始下载 pdf 文件到本地 , 而并不能在网页上浏览…

C规范编辑笔记(九)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) C规范编辑笔记(八) 正文&#xff1a; 今天我们来分享一下C规范编辑笔记第九篇&#xff0c;话不多说&#xff0c;我…

【聆思CSK6 视觉AI开发套件试用】初体验

本篇文章来自极术社区与聆思科技组织的CSK6 视觉AI开发套件活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;米樂 非常幸运能有评测这次的CSK6的机会。记录使用该套件进行开发的过程和感受。 套件介绍 CSK6是聆思科技推出的一款MCUDSPNPU的SoC芯片 套件…

免费pdf合并在线,这几个神仙网站请收好

对于经常要处理PDF文档的人来说&#xff0c;pdf合并如今已经是很常见的需求了。但是这个操作对一般人来说还有点难度&#xff0c;因此很多人都在寻找好用的免费pdf合并在线网站。今天小编就为大家吐血整理了工作几年来遇到的几个免费pdf合并在线的神仙网站。 1. Pdfio 这是一…

网络故障分析助您高效网上办公(一)

前言 信息中心负责人表示&#xff0c;有用户反馈&#xff0c;在通过VPN访问某一IP的80端口时连接时断时续。同时信息中心给到的信息是通过VPN&#xff1a;XXX.XXX.253.5访问IP地址XXX.XXX.130.200的80端口出现访问时断时续问题。 需要通过分析系统看一下实际情况&#xff0c;…

【Linux修炼】11.进程的创建、终止、等待、程序替换

每一个不曾起舞的日子&#xff0c;都是对生命的辜负。 进程的创建、终止、等待、程序替换本节重点1. 进程的创建1.1 fork函数初识1.2 fork的返回值问题1.3 写时拷贝1.4 创建多个进程2. 进程终止2.1 进程退出码2.2 进程如何退出3. 进程等待3.1 进程等待的原因3.2 进程等待的方法…

Uboot中的DM驱动模型

这一篇我们学习uboot中的驱动模型的初始化&#xff0c;在uboot中&#xff0c;驱动模型被称为Driver Model&#xff0c;简称DM。这种驱动模型为uboot中的各类驱动提供了统一的接口。 1. 数据结构及概念 DM模型主要依赖于下面四种数据结构&#xff1a; udevice&#xff0c;具有…

MySQL数据库闭包 Closure Table 表实现

1、 数据库闭包表简介 像MySQL这样的关系型数据库&#xff0c;比较适合存储一些类似表格的扁平化数据&#xff0c;但是遇到像树形结构这样有深度的数据&#xff0c;就很难驾驭了。 针对这种场景&#xff0c;闭包表&#xff08;Closure Table &#xff09;是最通用的设计&…

面试官:系统需求多变时如何设计?

面试官&#xff1a;我想问个问题哈&#xff0c;项目里比较常见的问题 面试官&#xff1a;我现在有个系统会根据请求的入参&#xff0c;做出不同动作。但是&#xff0c;这块不同的动作很有可能是会发生需求变动的&#xff0c;这块系统你会怎么样设计&#xff1f; 面试官&#…

FFmpeg简单使用:视频编码 ---- YUV转H264

基本流程 从本地读取YUV数据编码为h264格式的数据&#xff0c;然后再存⼊到本地&#xff0c;编码后的数据有带startcode。 与FFmpeg 示例⾳频编码的流程基本⼀致。 函数说明&#xff1a;avcodec_find_encoder_by_name&#xff1a;根据指定的编码器名称查找注册的编码器。 av…

第二十九章 数论——中国剩余定理与线性同余方程组

第二十九章 数论——中国剩余定理与线性同余方程组一、中国剩余定理1、作用&#xff1a;2、内容&#xff1a;3、证明&#xff1a;&#xff08;1&#xff09;逆元的存在性&#xff08;2&#xff09;验证定理的正确性4、代码实现&#xff1a;&#xff08;1&#xff09;步骤&#…

国产操作系统openEuler22.03配置yum源

作者&#xff1a;IT圈黎俊杰 本文选用的操作系统版本是openEuler22.03-LTS。openEuler是指操作系统的品牌英文名&#xff0c;中文名叫“欧拉”&#xff1b;22.03是指版本号&#xff08;openEuler以年月为版本号&#xff0c;22.03表示2022年03月发布的版本&#xff09;&#xff…

sonarqube——前端vue本地代码审查code review查看代码行数和注释率

目录一、环境二、操作1.启动2.中文3.使用三、过程踩坑1.sonarqube启动闪退2.解析报错 node 14.17一、环境 windows 64位 环境压缩包下载&#xff08;sonar9.8&#xff0c;jdk11&#xff0c;sonar-scanner&#xff09; 下载完成解压后&#xff0c;将 sonar-scanner-4.7.0.2747-…

curl 指令

勿以恶小而为之&#xff0c;勿以善小而不为---- 刘备 curl 是常用的命令行工具&#xff0c;用来请求 Web 服务器。 它的名字就是客户端&#xff08;client&#xff09;的 URL 工具的意思。 它的功能非常强大&#xff0c;命令行参数多达几十种 我们后端开发者&#xff0c; 可以…