当前位置: 首页 > news >正文

redis集群

文章目录

  • 节点
    • 启动节点
    • 集群数据结构
    • CLUSTER MEET命令的实现
  • 槽指派
    • 记录节点的槽指派信息
    • 专播节点的槽指派信息
    • 17.2.3记录集群所有槽的指派信息
    • CLUSTER ADDSLOTS命令的实现
  • 在集群中执行命令
    • 计算键属于哪个槽
    • 判断槽是否由当前节点负责处理
    • MOVED错误
    • 节点数据库的实现
  • 重新分片
  • ASK错误
    • CLUSTERSETSLOT IMPORTING 命令的实现
    • CLUSTER SETSLOT MIGRATING命令的实现
    • ASK错误
    • ASKING命令
    • ASK错误和MOVED错误的区别
  • 复制与故障转移
    • 故障检测
    • 故障转移
    • 选举新的主节点
  • 消息
    • MEET、PING、PONG消息的实现
    • FAL消息的实现
    • PUBLISH消息的实现
  • 总结

Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。

节点

Redis集群通常由多个节点(node)组成,在刚开始的时候,每个节点都是相互独立的,它们都处于一个只包含自己的集群当中,要组建一个真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。

连接各个节点的工作可以使用CLUSTERMEET命令来完成。

向一个节点node(已在集群内的节点)发送 CLUSTER MEET 命令,可以让node节点与ip和port所指定的节点进行握手(handshake),当握手成功时,node节点就会将ip和port所指定的节点添加到node节点当前所在的集群中。

启动节点

一个节点就是一个运行在集群模式下的Redis服务器,Redis服务器在启动时会根据 cluster-enabled 配置选项是否为yes来决定是否开启服务器的集群模式。

节点(运行在集群模式下的Redis服务器)会继续使用所有在单机模式中使用的服务器组件,比如说:

  • 节点会继续使用文件事件处理器来处理命令请求和返回命令回复。节点会继续使用时间事件处理器来执行serverCron函数,而jservercron
    函数又会调用集群模式特有的clustercron函数。clusterCron函数负责执行在集群模式下需要执行的常规操作,例如向集群中的其他节点发送Gossip消息,检查节点是否断线,或者检查是否需要对下线节点进行自动故障转移等。
  • 节点会继续使用数据库来保存键值对数据,键值对依然会是各种不同类型的对象。
  • 节点会继续使用RDB持久化模块和AOF持久化模块来执行持久化工作。
  • 节点会继续使用发布与订阅模块来执行PUBLISH、SUBSCRIBE等命令。
  • 节点会继续使用复制模块来进行节点的复制工作。
  • 节点会继续使用Lua脚本环境来执行客户端输人的Lua脚本。

除此之外,节点会继续使用redisServer结构来保存服务器的状态,使用
redisclient结构来保存客户端的状态,至于那些只有在集群模式下才会用到的数据,节点将它们保存到了cluster.h/clusterNode结构、cluster.h/clusterLink结构,以及cluster.h/clusterState结构里面,接下来的一节将对这三种数据结构进行介绍。

集群数据结构

clusterNode结构保存了一个节点的当前状态,比如节点的创建时间、节点的名字、节点当前的配置纪元、节点的IP地址和端口号等等。

每个节点都会使用一个clusterNode结构来记录自己的状态,并为集群中的所有其他节点(包括主节点和从节点)都创建一个相应的clusterNode结构,以此来记录其他节点的状态。

``
struct ClusterNode {

//创建节点的时间
mstime tctime ;
// 节点的名字,由40个十六进制字符组成
//例如68eef66d23420a5862208ef5b1a70055806f2f
char name [REDIS_CLUSTER_NAMELENIA] ;
// 节点标识
// 使用各种不同的标识值记录节点的角色(比如主节点或者从节点)
// 以及节点自前所处的状态(比如在线或者下线)
int flags;
//节点当前的配置纪元,用于实现故障转移
uint64_t configEpoch ;
//节点的IP地址
char ip[REDIS_IP_STR_LEN] ;
// 节点的端口号
int port:
// 保存连接节点所需的有关信息
clusterLink *link ;

}
``

clusterNode结构的 link 属性是一个clusterLink结构,该结构保存了连接节点所需的有关信息,比如套接字描述符,输人缓冲区和输出缓冲区:
``
typedef struct clusterLink {

//连接的创建时间
mstime_t ctime;

// TCP套接字描述符
int fd;

// 输出缓冲区,保存着等待发送给其他节点的消(message)。sds sndbut ;

//输入缓冲区,保存着从其他节点接收到的消息。
sds rcvbuf ;

// 与这个连接相关联的节点,如果没有的话就为NUL
struct ClusterNode *node ;

} clusterLink;
``

每个节点都保存着一个clusterState结构,这个结构记录了在当前节点的视角下,集群目前所处的状态,例如集群是在线还是下线,集群包含多少个节点,集群当前的配置纪元,诸如此类:
``
typedef stxuct clusterstate {

// 指向当前节点的指针
clusterNode *myself:
// 集群当前的配置纪元,用于实现故障转移
uint64_t currentEpoch ;
// 集群当前的状态:是在线还是下线
int state ;
// 集群中至少处理着一个精的节点的数量
int size ;
// 集群节点名单(包括myself节点)字典的键为节点的名字,字典的值为节点对应的clusterNode结构
dict *nodes;

} clusterstate ;
``

  • 结构的 currentEpoch属性的值为0,表示集群当前的配置纪元为0。
  • 结构的size属性的值为0,表示集群目前没有任何节点在处理槽,因此结构的state属性的值为REDISCLUSTERFAIL,这表示集群目前处于下线状态。
  • 结构的nodes字典记录了集群日前包含的n个节点,这n个节点分别由三个clusterNode结构表示,其中myself指针指向代表节点自己的clusterNode结构,而字典中的另外两个指针则分别指向代表其他节点的clusterNode结构,这两个节点是当前节点已知的在集群中的其他节点。
  • 节点的clusterNode结构的flags属性都是REDIS_NODE_MASTER,说明这几个节点都是主节点。

CLUSTER MEET命令的实现

通过向节点A发送CLUSTERMEET命令,客户端可以让接收命令的节点A将另一个节点B添加到节点A当前所在的集群里面。

收到命令的节点A将与节点B进行握手(handshake),以此来确认彼此的存在,并为将来的进一步通信打好基础:

  • 1)节点A会为节点B创建一个clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面。
  • 2)之后,节点A将根据CLUSTERMEET命令给定的IP地址和端口号,向节点B发送一条MEET消息(message)
  • 3)如果一切顺利,节点B将接收到节点A发送的MEET消息,节点B会为节点A创建一个clusterNode结构,并将该结构添加到自已的clusterState.nodes字典里面。
  • 4)之后,节点B将向节点A返回一条PONG消息。
  • 5)如果一切顺利,节点A将接收到节点B返回的PONG消息,通过这条PONG消息节点A可以知道节点B已经成功地接收到了自己发送的MEET消息。
  • 6)之后,节点A将向节点B返回一条PING消息。
  • 7)如果一切顺利,节点B将接收到节点A返回的PING消息,通过这条PING消息节点B可以知道节点A已经成功地接收到了自己返回的PONG消息,握手完成

之后,节点A会将节点B的信息通过Gossip协议传播给集群中的其他节点,让其他节点也与节点B进行握手,最终,经过一段时间之后,节点B会被集群中的所有节点认识。

槽指派

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

当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。说明集群下节点要正常工作需要先分配好槽位。这也合理,没有槽位就无法存储数据。

记录节点的槽指派信息

clusterNode结构的 slots 属性和 numslot 属性记录了节点负责处理哪些槽:
``
struct clusterNode {

unsigned char slots[16384/8]
int numsIots ;

};
``

  • slots属性是一个二进制位数组(bitarray),这个数组的长度为16384/8=2048个字节,共包含16384个二进制位。记录了该节点处理哪些槽位。
  • numslots属性则记录节点负责处理的槽的数量,也即是slots数组中值为1的二进制位的数量。

专播节点的槽指派信息

一个节点除了会将自己负责处理的槽记录在clusterNode结构的slots属性和numslots属性之外,它还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。

当节点A通过消息从节点B那里接收到节点B的slots数组时,节点A会在自己的clusterState.nodes字典中查找节点B对应的clusterNode结构,并对结构中的slots数组进行保存或者更新。

因为集群中的每个节点都会将自已的slots数组通过消息发送给集群中的其他节点,并且每个接收到slots数组的节点都会将数组保存到相应节点的clusterNode结构里面因此,集群中的每个节点都会知道数据库中的16384个槽分别被指派给了集群中的哪些节点。

17.2.3记录集群所有槽的指派信息

clusterState结构中的 slots 数组记录了集群中所有16384个槽的指派信息:
``
typedef struct clusterstate {

clusterNode *slots[16384] ;

}clusterstate;
``
slots数组包含16384个项,每个数组项都是一个指clusterNode结构的指针:

  • 如果slots[i]指针指向NULL,那么表示槽i尚未指派给任何节点。
  • 如果slots[i]指针指向一个clusterNode结构,那么表示槽i已经指派给了clusterNode结构所代表的节点。

如果只将槽指派信息保存在各个节点的clusterNode.s1ots数组里,会出现一些无法高效地解决的问题,而clusterState.slots数组的存在解决了这些问题:

  • 如果节点只使用clusterNode.slots数组来记录槽的指派信息,那么为了知道槽i是否已经被指派,或者槽i被指派给了哪个节点,程序需要遍历clusterState.nodes字典中的所有clusterNode结构,检查这些结构的slots数组,直到我到负责处理槽i的节点为止,这个过程的复杂度为ON,其中N为clusterState.nodes字典保存的clusterNode结构的数量。
  • 而通过将所有槽的指派信息保存在clusterState.slots数组里面,程序要检查槽i是否已经被指派,又或者取得负责处理槽1的节点,只需要访问clusterState.slots[i]的值即可,这个操作的复杂度仅为O(1)。

虽然clusterState.slots数组记录了集群中所有槽的指派信息但使用clusterNode结构的slots数组来记录单个节点的槽指派信息仍然是有必要的:

  • 因为当程序需要将某个节点的槽指派信息通过消息发送给其他节点时,程序只需要将相应节点的clusterNode.slots数组整个发送出去就可以了。
  • 另一方面,如果Redis不使用clusterNode.slots数组,而单独使用clusterState.slots数组的话,那么每次要将节点A的槽指派信息传播给其他节点时,程序必须先遍历整个clusterstate.slots数组,记录节点A负责处理哪些槽,然后才能发送节点A的槽指派信息,这比直接发送clusterNode.slots数组要麻烦和低效得多。

clusterState.slots数组记录了集群中所有槽的指派信息,而clusterNode.slots数组只记录clusterNode结构所代表的节点的槽指派信息,这是两个slots数
组的关键区别所在。

CLUSTER ADDSLOTS命令的实现

CLUSTER ADDSLOTS 命令接受一个或多个槽作为参数,并将所有输人的槽指派给接收该命令的节点负责。

在 CLUSTER ADDSLOTS 命令执行完毕之后,节点会通过发送消息告知集群中的其他节点,自己自前正在负责处理哪些槽。

在集群中执行命令

在对数据库中的16384个槽都进行了指派之后,集群就会进人上线状态,这时客户端就可以向集群中的节点发送数据命令了。

当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:

  • 如果键所在的槽正好就指派给了当前节点,那么节点直接执行这个命令。
  • 如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个 MOVED 错误,指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。

在这里插入图片描述

计算键属于哪个槽

节点使用 CRC16 算法来计算给定键key属于哪个槽。

判断槽是否由当前节点负责处理

当节点计算出键所属的槽i之后,节点就会检查自己在clusterState.slots数组中的项i,判断键所在的槽是否由自己负责:
1)如果clusterstate.slots[i]等于clusterState.myself,那么说明槽i由当前节点负责,节点可以执行客户端发送的命令。
2)如果clusterstate.slots[i]不等于clusterstate.myself,那么说明槽i并非由当前节点负责,节点会根据clusterstate.slots[i]指向的 clusterNode结构所记录的节点IP和端口号,向客户端返回MOVED错误,指引客户端转向至正在处理槽
i的节点。

MOVED错误

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

节点数据库的实现

集群节点保存键值对以及键值对过期时间的方式,与redis之数据库里面介绍的单机Redis服务器保存键值对以及键值对过期时间的方式完全相同。节点和单机服务器在数据库方面的一个区别是,节点只能使用0号数据库,而单机Redis服务器则没有这一限制。

重新分片

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

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

ASK错误

在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在自标节点里面。

当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键怡好就属于正在被迁移的槽时:

  • 源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。
  • 相反地,如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导人槽的目标节点,并再次发送之前想要执行的命令。

CLUSTERSETSLOT IMPORTING 命令的实现

clusterState 结构的 importing_slots_from数组记录了当前节点正在从其他节点导人的槽:
``
typedef struct clusterState {

clusterNode *importing_slots_from[163841] ;

} clusterState ;
``
如果importing_slots_from[i]的值不为NULL,而是指向一个clusterNode结构,那么表示当前节点正在从clusterNode所代表的节点导人槽 i 。

CLUSTER SETSLOT MIGRATING命令的实现

clusterState结构的migrating_slots_to数组记录了当前节点正在迁移至其他节点的槽:
``
typedef struct clusterstate {

clusterNode *migrating_slots_to[16384]

}Clusterstate;
``
如果migrating_slots_to[i]的值不为NULL,而是指向一个clusterNode 结构,那么表示当前节点正在将槽1迁移至clusterNode所代表的节点。

ASK错误

如果节点收到一个关于键key的命令请求,并且键key所属的槽i正好就指派给了这个节点,那么节点会尝试在自已的数据库里查找键key,如果找到了的话,节点就直接执行客户端发送的命令。

与此相反,如果节点没有在自己的数据库里找到键key,那么节点会检查自己的clusterstate.migrating_slots_to[i],看键key所属的槽i是否正在进行迁移,如果槽i的确在进行迁移的话,那么节点会向客户端发送一个ASK错误,引导客户端到正在导人槽i的节点去查找键key。

ASKING命令

ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识。

在一般情况下,如果客户端向节点发送一个关于槽i的命令,而槽i又没有指派给这个节点的话,那么节点将向客户端返回一个MOVED错误;但是,如果节点的
clusterState.importing_slots_from[i]显示节点正在导人槽i,并且发送命令的客户端带有REDISASKING标识,那么节点将破例执行这个关于槽i的命令一次,图展示了这个判断过程

在这里插入图片描述
当客户端接收到ASK错误并转向至正在导人槽的节点时,客户端会先向节点发送一个ASKING命令,然后才重新发送想要执行的命令,这是因为如果客户端不发送ASKING命令,而直接发送想要执行的命令的话,那么客户端发送的命令将被节点拒绝执行,并返回MOVED错误。

另外要注意的是,客户端的REDIS_ASKING标识是一个一次性标识,当节点执行了一个带有REDIS_ASKING标识的客户端发送的命令之后,客户端的REDIS_ASKING标识就会被移除。

ASK错误和MOVED错误的区别

ASK错误和MOVED错误都会导致客户端转向,它们的区别在于:

  • MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于槽i的MOVED错误之后,客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点。
  • 与此相反,ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施:在客户端收到关于槽i的ASK错误之后,客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点,但这种转向不会对客户端今后发送关于槽i的命令请求产生任何影啊,客户端仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点,除非ASK错误再次出现。

复制与故障转移

Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。

故障检测

集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果接收PING消息的节点没有在规定的时间内,向发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线(probable fail,PFAIL )

集群中的各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息,例如某个节点是处于在线状态、疑似下线状态(PFAIL),还是已下线状态(FAIL)

当一个主节点A通过消息得知主节点B认为主节点C进人了疑似下线状态时,主节点A会在自己的clusterstate.nodes字典中找到主节点C所对应的clusterNode结构,并将主节点B的下线报告(failurereport)添加到 clusterNode结构的fail_reports链表里面。

如果在一个集群里面,半数以上负责处理槽的主节点都将某个主节点x报告为疑似下线那么这个主节点将被标记为已下线(FAL)将主节点x标记为已下线的节点会向集群广播一条关于主节点x的EAIL消息,所有收到这条EAIL消息的节点都会立即将主节点标记为已下线。

故障转移

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

  • 1)复制下线主节点的所有从节点里面,会有一个从节点被选中
  • 2)被选中的从节点会执行SLAVEOFnoone命令,成为新的主节点。
  • 3)新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。
  • 4)新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽。
  • 5)新的主节点开始接收和自已负责处理的槽有关的命令请求,故障转移完成

选举新的主节点

新的主节点是通过选举产生的
以下是集群选举新的主节点的方法:

  • 1)集群的配置纪元是一个自增计数器,它的初始值为0。
  • 2)当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增
  • 3)对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票。
  • 4)当从节点发现自己正在复制的主节点进人已下线状态时,从节点会向集群广播一条CLUSTERMSG_TYPE_FAILOVERAUTH_REQUEST消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。
  • 5)如果一个主节点具有投票权(它正在负责处理槽)并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点。
  • 6)每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自已收到了多少条这种消息来统计自已获得了多少主节点的支持。
  • 7)如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1张支持票时,这个从节点就会当选为新的主节点。
  • 8)因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有N个主节点进行投票,那么具有大于等于N/2+1张支持票的从节点只会有一个,这确保了新的主节点只会有一个
  • 9)如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。

这个选举新主节点的方法和选举领头Sentinel的方法非常相似,因为两者都是基于Raft算法的领头选举(leaderelection)方法来实现的。

消息

集群中的各个节点通过发送和接收消息(message)来进行
通信,我们称发送消息的节点为发送者(sender),接收消息的节点为接收者(receiver)。
节点发送的消息主要有以下五种:

  • MEET消息:当发送者接到客户端发送的CLUSTER
    MEET命令时,发送者会向接收者发送MEET消息,请求接收者加人到发送者当前所处的集群里面。
  • PING消息:集群里的每个节点默认每隔一秒钟就会从已知节点列表中随机选出五个节点,然后对这五个节点中最长时间没有发送过PING消息的节点发送PING消息,以此来检测被选中的节点是否在线。除此之外,如果节点A最后一次收到节点B发送的PONG消息的时间,距离当前时间已经超过了节点A的cluster-node-timeout选项设置时长的一半,那么节点A也会向节点B发送PING消息,这可以防止节点A因为长时间没有随机选中节点B作为PING消息的发送对象而导致对节点B的信息更新带后。(注意:这里并非向每个节点发送PING,而是随机选取5个,取其中最久没发送过的发送PING)
  • PONG消息:当接收者收到发送者发来的MEET消息或者PING消息时,为了向发送者确认这条MEET消息或者PING消息已到达,接收者会向发送者返回一条PONG消息。另外,一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他节点立即刷新关于这个节点的认识,例如当一次故障转移操作成功执行之后,新的主节点会向集群广播一条PONG消息,以此来让集群中的其他节点立即知道这个节点已经变成了主节点,并且接管了已下线节点负责的槽。
  • FAIL消息:当一个主节点A判断另一个主节点B已经进人FAIL状态时,节点A会向集群广播一条关于节点B的FAIL消息,所有收到这条消息的节点都会立即将节点B标记为已下线。
  • PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令。

一条消息由消息头(header)和消息正文(data)组成。

MEET、PING、PONG消息的实现

Redis集群中的各个节点通过Gossip协议来交换各自关于不同节点的状态信息,其中Gossip协议由MEET、PING、PONG三种消息实现。

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 ping_sent ;

// 最后一次从该节点接收到PONG消息的时间
uint32_t pong_received;

//节点的IP地址
Char ip[16] ;

// 节点的端口号
uint16_t Port ;

//节点的标识值
uint16_t flags ;

} clusterMsgDataGossip

``

当接收者收到MEET、PING、PONG消息时,接收者会访问消息正文中的两个clusterMsgDataGossip结构,并根据自已是否认识clusterMsgDataGossip结构中记录的被选中节点来选择进行哪种操作:

  • 如果被选中节点不存在于接收者的已知节点列表,那么说明接收者是第一次接触到被选中节点,接收者将根据结构中记录的IP地址和端口号等信息,与被选中节点进行握手。
  • 如果被选中节点已经存在于接收者的已知节点列表,那么说明接收者之前已经与被选中节点进行过接触,接收者将根据clusterMsgDataGossip结构记录的信息对被选中节点所对应的clusterNode结构进行更新。

FAL消息的实现

当集群里的主节点A将主节点B标记为已下线(FAIL)时,主节点A将向集群广播一条关于主节点B的FAIL消息,所有接收到这条FAIL消息的节点都会将主节点B标记为已下线。

在集群的节点数量比较大的情况下,单纯使用Gossip协议来传播节点的已下线信息会给节点的信息更新带来一定延退,因为Gossip协议消息通常需要一段时间才能传播至整个集群,而发送FALL消息可以让集群里的所有节点立即知道某个主节点已下线,从而尽快判断是否需要将集群标记为下线,又或者对下线主节点进行故障转移。

PUBLISH消息的实现

当客户端向集群中的某个节点发送命令PUBLISH消息的时候,接收到PUBLISH命令的节点不仅会向channel频道发送消息message,它还会问集群广播一条SPUBLISH 消息,所有接收到这条PUBLISH消息的节点都会向channel 频道发送message消息。

总结

  • 节点通过握手来将其他节点添加到自己所处的集群当中。
  • 集群中的16384个槽可以分别指派给集群中的各个节点,每个节点都会记录哪些槽指派给了自己,而哪些槽又被指派给了其他节点。
  • 节点在接到一个命令请求时,会先检查这个命令请求要处理的键所在的槽是否由自已负责,如果不是的话,节点将向客户端返回一个MOVED错误,MOVED错误携带的信息可以指引客户端转向至正在负责相关槽的节点。
  • 对Redis集群的重新分片工作是由redis-trib负责执行的,重新分片的关键是将属于某个槽的所有键值对从一个节点转移至另一个节点。
  • 如果节点A正在迁移槽i至节点B,那么当节点A没能在自己的数据库中找到命令指定的数据库键时,节点A会向客户端返回一个ASK错误,指引客户端到节点B继续查找指定的数据库键。
  • MOVED错误表示槽的负责权已经从一个节点转移到了另一个节点,而ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施。
  • 集群里的从节点用于复制主节点,并在主节点下线时,代替主节点继续处理命令请求。
  • 集群中的节点通过发送和接收消息来进行通信,常见的消息包括MEET、PING、PONG、PUBLISHSFAIL五种。

相关文章:

redis集群

文章目录 节点启动节点集群数据结构CLUSTER MEET命令的实现 槽指派记录节点的槽指派信息专播节点的槽指派信息17.2.3记录集群所有槽的指派信息CLUSTER ADDSLOTS命令的实现 在集群中执行命令计算键属于哪个槽判断槽是否由当前节点负责处理MOVED错误节点数据库的实现 重新分片ASK…...

Spring Boot整合DeepSeek实现AI对话(API调用和本地部署)

本篇文章会分基于DeepSeek开放平台上的API,以及本地私有化部署DeepSeek R1模型两种方式来整合使用。 本地化私有部署可以参考这篇博文 全面认识了解DeepSeek利用ollama在本地部署、使用和体验deepseek-r1大模型 Spring版本选择 根据Spring官网的描述 Spring AI是一…...

git如何把多个commit合成一个

在 Git 中,如果你想把多个提交(commit)合并成一个,可以使用 git rebase 或 git reset 来完成。下面是两种常用方法: 方法一:使用 git rebase(推荐) git rebase 是合并多个提交为一…...

flutter启动后不显示文字,中文字体不显示

Flutter 3.29.0-1.0.pre.114运行报错: Failed to load font Roboto at https://fonts.gstatic.com/s/roboto/v32/KFOmCnqEu92Fr1Me4GZLCzYlKw.woff2 解决: 下载roboto字体,并将字体(Roboto-Regular.ttf)放入app代码下…...

跟着李沐老师学习深度学习(十一)

经典的卷积神经网络 在本次笔记中主要介绍一些经典的卷积神经网络模型,主要包含以下: LeNet:最早发布的卷积神经网络之一,目的是识别图像中的手写数字;AlexNet: 是第一个在大规模视觉竞赛中击败传统计算机…...

小结:OSPF的网络类型,LSA

OSPF(Open Shortest Path First)是一个基于链路状态的内部网关协议(IGP)。以下是对OSPF网络类型、LSA类型、序列号与Age作用,以及相关配置指令的详细讲解。 一、OSPF的网络类型 OSPF支持多种网络类型,不同…...

用vue3写一个好看的wiki前端页面

以下是一个使用 Vue 3 Element Plus 实现的 Wiki 风格前端页面示例&#xff0c;包含现代设计、响应式布局和常用功能&#xff1a; <template><div class"wiki-container"><!-- 头部导航 --><el-header class"wiki-header"><d…...

Redis常见数据结构

目录 基本介绍 特点&#xff1a; 全局命令 数据类型&#xff1a; String Hash List Set Zset 基本介绍 Redis是一个在内存中存储数据的中间件,可作为数据库&#xff0c;缓存&#xff0c;消息队列等。 特点&#xff1a; 持久化&#xff1a;Redis会把数据存储在内存中…...

JavaScript中Map和Set数据结构详解

目录 一、Map 1. 定义与特点 2. 创建Map对象 3. 常用方法 4. 遍历Map 5. Map与对象的区别 二、Set 1. 定义与特点 2. 创建Set对象 3. 常用方法 4. 遍历Set 5. 应用场景 数据去重 集合运算 遍历集合 一、Map 1. 定义与特点 Map 是 JavaScript ES6 中新增的一种数…...

【devops】 Git仓库如何fork一个私有仓库到自己的私有仓库 | git fork 私有仓库

一、场景说明 场景&#xff1a; 比如我们Codeup的私有仓库下载代码 放入我们的Github私有仓库 且保持2个仓库是可以实现fork的状态&#xff0c;即&#xff1a;Github会可以更新到Codeup的最新代码 二、解决方案 1、先从Codeup下载私有仓库代码 下载代码使用 git clone 命令…...

deepseek:三个月备考高级系统架构师

一、备考总体规划&#xff08;2025年2月11日 - 2025年5月&#xff09; 1. 第一阶段&#xff1a;基础夯实&#xff08;2025年2月11日 - 2025年3月10日&#xff09; 目标&#xff1a;快速掌握系统架构师考试的核心知识点。 重点内容&#xff1a; 计算机组成原理、操作系统、数据…...

设计模式Python版 命令模式(上)

文章目录 前言一、命令模式二、命令模式示例 前言 GOF设计模式分三大类&#xff1a; 创建型模式&#xff1a;关注对象的创建过程&#xff0c;包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。结构型模式&#xff1a;关注类和对象之间的组合&…...

部署 DeepSeek R1各个版本所需硬件配置清单

DeepSeek-R1 通过其卓越的推理性能和灵活的训练机制&#xff0c;在 2025 年的春节期间受到了广泛关注。 DeepSeek-R1 是一款高性能的 AI 推理模型&#xff0c;主要通过强化学习技术来增强模型在复杂任务场景下的推理能力。 在本地部署 DeepSeek-R1 时&#xff0c;尤其是完整的…...

【NLP 21、实践 ③ 全切分函数切分句子】

当无数个自己离去&#xff0c;我便日益坦然 —— 25.2.9 一、jieba分词器 Jieba 是一款优秀的 Python 中文分词库&#xff0c;它支持多种分词模式&#xff0c;其中全切分方式会将句子中所有可能的词语都扫描出来。 1.原理 全切分方式会找出句子中所有可能的词语组合。对于一…...

pycharm如何对比两个文档

在 PyCharm 中对比两个文档&#xff0c;并使它们同步滚动&#xff0c;可以按照以下步骤操作&#xff1a; 在 PyCharm 的文件列表中右键点击需要对比的其中一个文件。选择 “Compares With” 选项。选择第二个文件进行对比。...

React 第二十五节 <Fragment></Fragment> 的用途以及使用注意事项详解

文章如果错误偏差&#xff0c;烦请及时批评指正 一、为什么要使用 <Fragment>&#xff1f; 因为在 React 中&#xff0c;组件必须返回单个根元素。当我们尝试直接返回相邻的 JSX 元素时&#xff1a; function BrokenComponent() {return (<h1>标题</h1><…...

探秘Hugging Face与DeepSeek:AI开源世界的闪耀双子星

目录 一、引言&#xff1a;AI 开源浪潮的澎湃二、Hugging Face&#xff1a;AI 开源社区的基石&#xff08;一&#xff09;起源与发展历程&#xff08;二&#xff09;核心技术与特色&#xff08;三&#xff09;在 AI 领域的广泛应用 三、DeepSeek&#xff1a;东方崛起的 AI 新势…...

centos 和 ubuntu 区别

一、发行版与支持 1. CentOS 是基于 Red Hat Enterprise Linux&#xff08;RHEL&#xff09;源代码重新编译而成的社区版&#xff0c;遵循开源协议。一般由社区进行维护&#xff0c;每 7 年左右发布一个主要版本&#xff0c;注重稳定性和长期支持&#xff0c;适合对系统稳定性…...

windows,docker停止所有容器

CMD命令窗口 你可以尝试使用以下命令来停止所有正在运行的Docker容器&#xff1a; FOR /f "tokens*" %i IN (docker ps -q) DO docker stop %i这条命令的工作原理是&#xff1a; docker ps -q 列出所有正在运行的容器的ID。 FOR /f "tokens*" %i IN (c…...

AI赋能前端性能优化:效率与体验的完美平衡

在当今快节奏的互联网世界中&#xff0c;用户体验至关重要。一个加载缓慢、反应迟钝的网站或应用&#xff0c;很容易导致用户流失。而前端性能优化&#xff0c;作为提升用户体验的关键环节&#xff0c;却常常面临着诸多挑战。复杂的代码逻辑、庞大的资源文件、以及难以捉摸的性…...

8、《5分钟构建RESTful API:Spring Boot Web开发入门》

5分钟构建RESTful API&#xff1a;Spring Boot Web开发入门 一、RESTful API核心认知 REST&#xff08;Representational State Transfer&#xff09;通过HTTP协议实现资源操作&#xff0c;其核心特征包括&#xff1a; 资源以URI标识&#xff08;/api/users&#xff09;通过…...

【Java八股文】01-Java基础面试篇

【Java八股文】01-Java基础面试篇 概念Java特点Java为什么跨平台JVM、JDK、JRE关系 面向对象什么是面向对象&#xff0c;什么是封装继承多态&#xff1f;多态体现的方面面向对象设计原则重载重写的区别抽象类和实体类区别Java抽象类和接口的区别抽象类可以被实例化吗 深拷贝浅拷…...

读取本地excel删除第一行,并生成List数组

在 pom.xml 里添加如下依赖&#xff1a; <dependencies><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.3</version></dependency><dependency><groupId>org.ap…...

【核心特性】Go 模块化开发入门(go mod 深度解析)

Go 模块化开发入门&#xff08;go mod 深度解析&#xff09; 一、引言 在 Go 语言的开发过程中&#xff0c;随着项目规模的不断扩大&#xff0c;依赖管理变得愈发重要。Go Modules 作为 Go 语言官方的依赖管理工具&#xff0c;自 Go 1.11 版本引入后&#xff0c;极大地简化了项…...

【新品解读】AI 应用场景全覆盖!解码超高端 VU+ FPGA 开发平台 AXVU13F

「AXVU13F」Virtex UltraScale XCVU13P Jetson Orin NX 继发布 AMD Virtex UltraScale FPGA PCIE3.0 开发平台 AXVU13P 后&#xff0c;ALINX 进一步研究尖端应用市场&#xff0c;面向 AI 场景进行优化设计&#xff0c;推出 AXVU13F。 AXVU13F 和 AXVU13P 采用相同的 AMD Vir…...

Flask和Django相比哪个更适合新手?

Flask 与 Django:哪个更适合新手? 对于新手来说,选择 Flask 还是 Django 主要取决于你的具体需求和项目复杂度。以下是两者的详细对比,帮助你做出选择: 1. Flask 优点 简单易用:Flask 是一个轻量级的微框架,代码简洁,易于理解和上手。适合初学者快速入门。灵活性高:…...

高通android WIFI debug

参考高通文档&#xff1a;80-76240-16_REV_AA_Wi-Fi_Debug_Techniques 大纲 一、 WLAN Debug Logs –logcat ■ Logcat log logcat is a command-line tool that dumps the log of system messages, ■ Including stack traces when the device throws an error. ■ Need t…...

ADC 的音频实验,无线收发模块( nRF24L01)

nRF24L01 采用 QFN20 封装&#xff0c;有 20 个引脚&#xff0c;以下是各引脚的详细介绍&#xff1a; 1. 电源引脚 ◦ VDD&#xff1a;电源输入端&#xff0c;一般接 3V 电源&#xff0c;为芯片提供工作电压&#xff0c;供电电压范围为 1.9V&#xff5e;3.6V。 ◦ VSS&#xf…...

Swagger2 Knife4jConfig 配置,父子项目swagger扫描多个子模块中的Controller生成接口文档:

问题: 需求父子项目swagger扫描多个子模块中的Controller生成接口文档: Swagger2Knife4jConfig 配置放在了 springboot-app 启动项目里&#xff0c;而 tiger-web 和 tiger-web2 这两个子项目有自己的 Controller。这种情况下&#xff0c;Swagger 可能无法正确扫描 tiger-web 和…...

Unity UI 道路线跟随:让图标沿道路轨迹移动

在 Unity UI 开发中&#xff0c;有时需要让图标沿着一条道路轨迹移动&#xff0c;比如地图上的车辆行驶动画、导航路径指示等。本文介绍如何基于 UI 图片中的道路线生成曲线&#xff0c;并使用 Slider 控制图标沿轨迹运动&#xff0c;适用于 UI 导航、路径跟随动画等场景。 1. …...

Springboot核心:拦截器

拦截器是 Spring 框架提供的核心功能之一,属于一种设计模式&#xff0c;它允许在某个方法调用或请求处理前后执行自定义逻辑&#xff0c;是一种AOP&#xff08;面向切面编程&#xff09;机制。它们可以在不修改原有业务逻辑的情况下&#xff0c;对进入系统的请求进行预处理和后…...

【AI学习笔记】基于Unity+DeepSeek开发的一些BUG记录解决方案

【AI学习笔记】基于UnityDeepSeek开发的一些BUG记录&解决方案 背景前摇&#xff1a;&#xff08;省流可不看&#xff09; Unity是大学学的&#xff0c;AI是研究生学的&#xff0c;DeepSeek是第一份实习偷师的&#xff0c;三合一的梦是最近开始做的&#xff0c;BUG是今天遇…...

六西格玛设计培训如何破解风电设备制造质量与成本困局

2023年&#xff0c;中国风电行业装机容量突破4.3亿千瓦&#xff0c;稳居全球第一&#xff0c;但高速扩张背后暗藏隐忧&#xff1a; 质量痛点&#xff1a;叶片开裂、齿轮箱故障等缺陷频发&#xff0c;运维成本占项目全生命周期成本超30%&#xff1b;成本压力&#xff1a;原材料…...

xml 和json互转工具

工具1 有个问题会导致springboot返回捕获的异常编程xml格式 import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.json.JSONObject; import org.json.XML; import org.w3c.dom.Document;import javax.xm…...

【STM32】舵机SG90

1.舵机原理 舵机内部有一个电位器&#xff0c;当转轴随电机旋转&#xff0c;电位器的电压会发生改变&#xff0c;电压会带动转一定的角度&#xff0c;舵机中的控制板就会电位器输出的电压所代表的角度&#xff0c;与输入的PWM所代表的角度进行比较&#xff0c;从而得出一个旋转…...

【RK3588嵌入式图形编程】-SDL2-鼠标输入处理

鼠标输入处理 文章目录 鼠标输入处理1、概述2、鼠标移动事件3、鼠标点击事件4、鼠标点击位置5、鼠标双击6、鼠标进入和离开事件7、总结在本文中,将介绍如何在 SDL2 中检测和处理鼠标输入事件,包括鼠标移动、按钮点击以及窗口进入/退出。 1、概述 在本文中,我们将详细介绍如…...

知识管理成功:关键指标和策略,研究信息的投资回报率

信息过载会影响生产力。没有人工智能的帮助&#xff0c;信息过载会影响生产力。大量的可用信息&#xff0c;知识工作者不仅仅是超负荷工作&#xff1b;他们感到不知所措&#xff0c;他们倾向于浪费时间&#xff08;和脑细胞&#xff09;来应付他们被大量的数据抛向他们&#xf…...

Linux权限提升-内核溢出

一&#xff1a;Web到Linux-内核溢出Dcow 复现环境&#xff1a;https://www.vulnhub.com/entry/lampiao-1,249/ 1.信息收集&#xff1a;探测⽬标ip及开发端⼝ 2.Web漏洞利⽤&#xff1a; 查找drupal相关漏洞 search drupal # 进⾏漏洞利⽤ use exploit/unix/webapp/drupal_dr…...

Bootstrap5 折叠功能详解

Bootstrap5 折叠功能详解 引言 Bootstrap 是一个流行的前端框架&#xff0c;它提供了许多组件和工具&#xff0c;使得开发者可以轻松构建响应式网站。在 Bootstrap5 中&#xff0c;折叠&#xff08;Collapse&#xff09;组件得到了增强&#xff0c;使其更加灵活和易于使用。本…...

JAVA:Spring Boot 项目中自定义 Banner 的技术指南

1、简述 在 Spring Boot 项目中&#xff0c;当应用启动时会显示默认的 Spring 标志和版本信息。定制化的启动 Banner 不仅可以美化应用&#xff0c;还能在项目中增加个性化的品牌印记&#xff0c;甚至可以提供一些关键信息。本文将介绍如何在 Spring Boot 项目中自定义启动 Ba…...

【虚幻引擎UE】UE4.23到UE5.5的核心功能变化

简单总结从UE4.23到UE5.5&#xff0c;虚幻引擎的重大变化&#xff1a; 1. WebGL/HTML5 平台支持和像素流 UE4.23-UE4.25&#xff1a;移除官方HTML5支持&#xff0c;改为社区插件维护。 但通过第三方插件&#xff08;如WebAssemblyWebGPU&#xff09;可在浏览器运行部分项目。U…...

信号检测和信道均衡的联系

1. 系统模型 假设一个通信系统的数学模型如下&#xff1a; 发送信号&#xff1a; s [ s 1 , s 2 , … , s N ] T \mathbf{s} [s_1, s_2, \dots, s_N]^T s[s1​,s2​,…,sN​]T&#xff0c;其中 s i s_i si​ 是发送符号。信道矩阵&#xff1a; H \mathbf{H} H&#xff08;…...

在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档教程

既然我们已经在本地部署了DeepSeek,肯定希望能够利用本地的模型对自己软件开发、办公文档进行优化使用,接下来就先在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档的教程奉上。 前提: (1)已经部署好了DeepSeek,可以看我的文章:个人windows电脑上安装DeepSe…...

26、深度学习-自学之路-NLP自然语言处理-理解加程序,怎么把现实的词翻译给机器识别。

一、怎么能让机器能够理解我们的语言呢&#xff0c;我们可以利用神经网络干很多的事情&#xff0c;那么我们是不是也可以用神经元做自然语言处理呢&#xff0c;现在很多的实际应用已经说明了这个问题&#xff0c;可以这么做。 那我们考虑一下该怎么做&#xff0c;首先我们应该…...

微信服务号推送消息

这里如果 没有 就需要点新的功能去申请一下 申请成功之后就可以设置模版消息 推送到用户接受的页面是 需要后端调用接口 传递token 发送给客户...

Excel常用操作

Excel常用操作 学习资源 37_电子表格处理考点精讲_设置数据格式_哔哩哔哩_bilibili 快速输入数据与编辑数据 一个工作簿可以包含多个工作表 特殊数据的添加格式 输入负数, 例如-3、-5 常规输入, 直接输入-3、-5;使用(), 例如在单元格中输入(3)回车即可变为-3;上述括号不区分中…...

得物端智能视频封面推荐

一、背景 什么要做智能封面&#xff1f; 用户可以在得物购物&#xff0c;也可以在得物社区分享自己的生活。 得物社区中的视频使用双列流&#xff0c;每条内容包含封面、标题等。 对得物社区的创作者而言&#xff0c;选择视频封面是创作链路的重要环节。对得物社区的消费者…...

【Stable Diffusion部署至GNU/Linux】安装流程

以下是安装Stable Diffusion的步骤,以Ubuntu 22.04 LTS为例子。 显卡与计算架构介绍 CUDA是NVIDIA GPU的专用并行计算架构 技术层级说明CUDA Toolkit提供GPU编译器(nvcc)、数学库(cuBLAS)等开发工具cuDNN深度神经网络加速库(需单独下载)GPU驱动包含CUDA Driver(需与CUDA …...

介绍 Liquibase、Flyway、Talend 和 Apache NiFi:选择适合的工具

在现代软件开发中&#xff0c;尤其是在数据库管理和数据集成方面&#xff0c;选择合适的工具至关重要。本文将介绍四个流行的工具&#xff1a;Liquibase、Flyway、Talend 和 Apache NiFi&#xff0c;分析它们的应用、依赖以及如何选择适合的工具。 1. Liquibase 简介&#xff…...

Unity 接入Tripo 文生模型,图生模型

官方网站&#xff1a;https://www.tripo3d.ai/app/home自行注册账号并且登陆下载Unity插件&#xff1a;https://cdn-web.tripo3d.ai/plugin/tripo-unity.zip申请apikey&#xff1a; https://platform.tripo3d.ai/api-keys使用&#xff08;后续过程就按照第二步下载的插件里面的…...