redis系列(七)redis集群

    1.  redis 集群

           我们都知道数据库集群,redis 集群是 redis 提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。

  • 节点

          一个 redis 集群通常由多个节点组成,在刚开始的时候,每个节点都是相互独立的,他们分别处于一个只含有他自己的集群中,要组建一个真正的集群,我们需要把多个节点连接起来,构成一个包含多节点的集群。

          在 redis 中我们可以通过 CLUSTER MEET 命令让节点与 ip 和 port 所指定的节点进行握手,握手成功时,这个节点就会将 ip 和 port 指定的节点添加到自己所在的集群中。

          一个节点其实就是一个运行的 redis 服务器,要使用节点首先我们要启动节点,即将 redis.cong 中的 cluster-enabled 配置选项改为 yes。

          集群的数据结构

          cluster.h/clusterNode 结构保存了一个节点的当前状态。

         

          mstime_t:节点的创建时间

          name:节点的名字,有40个十六进制字符组成

          flag:节点标识,使用不同的标识值记录节点的角色,已经节点目前的状态

          unit64_t:节点当前的配置纪元,用于实现故障转移

          ip:节点的 ip

          port:节点的端口

          *link:保存连接节点所需要的相关信息


          clusterNode 中的 link 属性是一个 clusterLink 结构,该节点保存了连接点所需要的相关信息,比如套接字描述符,输入缓冲区和输出缓冲区

         

          mstime_t ctime: 连接的创建时间

          fd:套接字描述符

          sndbuf:输出缓冲区,保存着将要发送给其他节点的信息

          rcvbuf:输入缓冲区,保存着其他节点发来的信息

          clusterNode *node:与这个连接相关联的节点,如果没有的话为 NULL


          最后,每个节点都保存着一个 clusterState 结构,这个结构记录了当前节点的视角下,集群目前所处的状态。

         

          myself:指向当前节点的指针

          currentEpoch:集群当前的配置纪元,永远故障转移

          state:集群当前的状态,是上线还是下线

          size:集群中至少处理着一个槽的节点数量

          nodes:集群中节点集合,字典的 key 为节点的名字,value 为节点的结构

          看完这些结构之后,我们来看看从发送命令到将节点成功添加到集群中的过程:首先,我们先假设有三个节点,A ,B, C。A 和 B 是一个集群中的节点,C 是单独的节点、

  • 槽指派

          redis集群中是通过分片的方式保存数据库中的键值对,集群的整个数据库被分为16384个槽(slot),数据库中的每一个键都属于这些槽中的一个,集群中的每个节点可以处理0个或最多16384个槽。只有当服务器中所有的槽都被处理时,集群才属于上线状态,只要有一个槽没有被处理,那么集群就处于下线状态。

          通过 CLUSTER ADDSLOTS 命令,我们可以将一个或者多个槽指派给节点负责(CLUSTER ADDSLOTS <slot> [slot . . . ])

          槽信息

          在 clusterNode 结构中还有 slots[CLUSTER_SLOTS/8] 和 numslots 这两个属性。

          solts 是一个长度为16384/8 个字节的数组,共含有16384 个二进制位,  其以 0 为起点,以16383 为终点作为索引,对 slots 中 16384 个二进制位进行编号,并根据这个位上的值来标志这个槽是否被占用,如果是 1 表示已经被占用,如果是 0 表示未被占用。

          而 numlosts  就是当前节点所负责处理的槽的数量,也就是这些二进制位中值为 1 的个数。

          一个节点除了会将自己处理的槽信息记录在 clusterNode 中的 slots 中外 ,还会将自己的 slots 数组发送给其他的节点,例如节点 A 向 节点 B 发送了自己的 slots 数组,节点 B 收到后,会将自己 clusterState 中的 nodes的 字典中找到 A 所对应的 clusterNode 结构,并将结构中的 slots 进行更新。

          除了每个节点会记录自己处理的槽信息之外,每个节点还会记录集群中所有节点的操心

  • 在集群中执行命令

          在对数据库中的16384个槽都进行了指派之后,集群就会进入上线状态,这时客户端就可以向集群中的节点发送数据命令了,当客户端向节点发送与数据库键相关的命令的时候,接受命令的节点就会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派了自己。如果指派给了自己,那么当前节点就会执行这条命令,如果没有指派给自己,那么节点就会向客户端返回一个MOVED的错误、指引客户端指向正确的节点,重新发送之前想要执行的命令。

          在集群中创建一个键的时候,首先会通过 CRC16 算法计算出一个校验和,然后在用这个值进行 & 16383运算,得出的值就是这个key的槽号,同样的就可以通过这个方式计算出 key 所在的节点位置。当我们知道节点的位置之后,节点就可以检查自己在 clusterState.slots 数组中的这一项,然后与 clusterState.myself 进行比较,如果相同说明这个槽由自己负责,反之,不负责,并通过 clusterState.slots[ i ] 指向的 clusterNode 结构所记录的节点 IP 和端口号,向客户端返回 MOVED 错误,指引客户端转向正确的节点(MOVED 的格式为 MOVED <slot> <ip>:<port>)。

        

  • 重新分片

          Redis 集群的重新分片操作可以将任意数量已经指派给某个节点的槽改为指派给另一个节点,并且相关槽所属的键值对也会从源节点被移动到目标节点。重新分片可以在线上运行,在重新分片的过程中,集群不必下线,并且源节点和目标节点都可以继续执行处理命令请求。

          原理:

          Redis 集群的重新分片操作是由 Redis 的集群管理软件 redis-trib 负责执行的,Redis 提供了重新分片的所有命令,而 redis-trib 则通过向源节点和目标节点发送这些命令来实现重新分片。

          首先我们先看一下对于一个槽的重新分片处理流程:

  1. 对目标节点发送 CLUSTER SETSLOT<slot> IMPORTING <source-id>,让目标节点准备好从源节点导入属于slot的键值对。
  2. 对源节点发送 CLUSTER SETSLOT<slot> MAGRATING <target-id> 命令,让源节点准备好将属于 slot 的键值对迁移到目标节点。
  3. 向源节点发送 CLUSTER GETKEYSINSLOT <slot> <count> 命令,获得最多 count 个属性 slot 的键值对的键名。
  4. 对于上面获得的每一个键名,都想源节点发送一条 MAGRATE <target_id> <port> <key_name> 0 <timeout> 命令,将选中的键原子的从源节点迁移至目标节点。
  5. 重复3 和 4
  6. 向集群中的任意一个节点发送 CLUSTER SETSLOT <> NODE <> 命令,将槽 slot 指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽 slot 已经指派给了目标节点。

         

  • 复制和故障转

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

          首先向一个节点发送命令:CLUSTER REPLICATE <node_id> 可以让接受命令的节点成为node_id 所指定节点的从节点,并开始对主节点进行复制。接收命令的节点首先会在自己的 clusterState.nodes 字典中找到 node_id 所对应节点的 clusterNode 结构,并将自己的 clusterState.myself.slaveof 指向这个结构,以此来记录这个节点正在复制的主节点。然后节点会修改自己在 clusterState.myself.flags 中的属性,关闭原来的 REDIS_NODE_MASTER 标识,打开 REDIS_NODE_SLAVE 标识,表示这个节点已经从原来的主节点变为从节点。最后节点调用复制代码进行复制。

         

          故障检测

          集群中的每个节点都会定期的向集群中的其他节点发送 PING 消息,以此来检测对方是否在线,如果接收 PING 消息的节点没有在规定的时间内,向发送 PING 消息的节点返回 PONG 消息,那么发送 PING 消息的节点就会将接收 PING 消息的节点标记为疑似下线。集群中的节点会通过互相消息的方式来交换集群中各个节点的状态信息。当一个主节点 A 通过消息得知主节点 B 认为主节点 C 疑似处于下线状态时,主节点 A 会在自己的 clusterNode.nodes 中找到主节点 C 对应的 clusterNode 结构,并将主节点 B  的下线报告添加到 clusterNode 结构的 fail_reports 链表里面。如果在一个集群里面,半数以上的负责处理槽的主节点都将某个主节点 x 报告为疑似下线,那么这个主节点 x 将被标记为已下线状态,将其标记为下线的节点会立即向集群广播。

         

          故障转移

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

  1.  在这个主节点的从节点里面,会有一个从节点被选中。
  2. 被选中的从节点会执行 SLAVEOF no one 命令,成为新的主节点。
  3. 新的主节点会撤销所有对已下线的主节点的槽指派,并将这些槽指向自己
  4. 新的主节点会向集群广播一条 PONG 消息,这条 PONG 消息会让集群中的其他节点立马知道这个节点成为了新的主节点,并且接管了原来主节点处理的槽。
  5. 新的主节点开始接收和自己处理的槽的相关命令请求,故障转移完成。

         

          选取新的主节点

          我们都知道主从模式下的选举模式,redis 的主从也是通过选举来选择主节点的,集体过程如下:

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

          集群中的各个节点都是通过消息来进行通讯的,发送消息的节点称为发送者,接收消息的称为接收者。

          节点发送的消息主要有以下五种:

           MEET 消息:当发送者接到客户端发送的 LUSTER MEET 命令时,发送者会向接收者发送 MEET 消息,请求接收者加到 发送者当前的集群中。

           PING 消息:集群中的每个节点默认每隔一秒就会从已知节点列表中随机选出五个节点,然后从这五个节点中选出一个最长时间没有发送过 PING 消息的节点发送 PING 消息,以此来检测被选中的节点是否在线。除此之外,如果节点 A 最后一次收到节点 B 发送的 PONG 消息的时间,距离当前已经超过了节点 A 的 cluster-node-timeout 选项设置时长的一半,那么节点 A 也会向节点 B 发送消息,这可以防止节点 A 因为长时间没有随机选中 B 作为 PING 消息的发送对象而导致对节点 B 的信息更新泄后。

           PONG 消息:当接收者收到发送者发来的 MEET 消息或者 PING 消息时,为了向发送者确认这条 MEET 或者 PING 消息已经到达,接收者会向发送者发回一条 PONG 消息。另外,一个节点也可以通过向集群中广播自己的 PONG 消息来让集群中的其他节点立即刷新关于这个节点的认识。

           FAIL 消息:当一个主节点 A 判断另一个主节点 B 已经进入 FAIL 状态时,节点 A 会向集群中立即广播一条节点 B 下线的通知,所有收到这条消息的节点都会立即将节点 B 标记为已下线。

           PUBLISH 消息:当节点接收一个 PUBLISH 命令时,节点会执行这个命令,并向集群广播一条 PUBLISH 消息,所有收到这条消息的节点都会执行相同的 PUBLISH 命令。

           消息头

           节点发送的所有消息都由一个消息头包裹,消息除了包含消息正文之外,还记录了消息发送者自身的一些信息,因为这些信息也会被消息接收者用到,所以我们可以认为消息头本身也是消息的一部分。

           每个消息头都是由一个 cluster.h/clusterMsg 结构表示:

          

           totlen:消息的长度

           type:消息的类型

           count:消息正文的节点数量

           urrentEpoch:发送者所处的纪元

           sender:发送者的名字

           myslots:发送者目前的槽指派信息

           data:消息正文

          

           MEET、PING、PONG 消息的实现

           redis 集群中的各个节点通过 Gossip 协议来交换各自关于不同节点的状态信息,其中 Gossip 协议由 MEET、PING、PONG 三种消息实现,这三种消息的正文都由两个 cluster.h/c;usterMsgDataGossip 结构组成:

           因为 MEET、PING、PONG 三种消息都使用相同的消息正文,所以节点通过消息头的 type 属性来判断一条消息是 MEET 消息,PING 消息还是 PONG 消息。

           每次发送 MEET、PING、PONG 消息时,发送者都从自己的已知节点列表中随机选出两个节点,并将这两个被选中的信息分别保存到两个 clusterMsgDataGossip 结构里面。

           clusterMsgDataGossip 结构记录了被选中节点的名字,发送者被选中节点最后一次发送和接收 PING 消息和 PONG 消息的时间戳,被选中节点的 IP 地址和端口号,以及被选中节点的标识值。

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

           1. 如果被选中节点不存在于接收者的已知节点列表,那么说明接收者是第一次接触到被选中节点,接收者将根据结构中记录的 IP 地址和端口等信息,与被选中节点进行握手。    

           2.如果被选中节点已经存在于接收者的已知节点列表,那么说明接收者之前已经与被选中节点进行过接触,接收者将根据 clusterMsgDataGossip 结构记录的信息,对被选中节点所对应的 clusterNode 结构进行更新。

            Fall 消息的实现

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

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

            PUBLISH  消息的实现    

            当客户端向集群中的某个节点发送命令 PUBLISH <channel> <message> 的时候,接收到 PUBLISH 命令的节点不会向 channel 频道发送消息 message ,它还会向集群中广播一条 PUBLISH 消息,所有收到这条消息的节点都会向 channel 频道发送 message 消息。换句话说,向集群中的某个节点发送这条命令将导致集群中的所有节点都会向 channel 频道发送 message 消息。

            clusterMsgDataPublish 结构的 bulk_data 属性是一个节点数组,这个字节数组保存了客户端通过 PUBLISH 命令发送给节点的 channel 参数和 message 参数,而结构的 channel_len 和 message_len 则分别保存了 channel 参数的长度和 message 参数的长度:

               

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值