十四、集群(redis)

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

一、节点

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

1.1 启动节点

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

 

节点(运行在集群模式下的Redis服务器)会继续使用所有在单机模式中使用的服务器 组件
            比如时间和事件事件
            AOF&RDB持久化

1.2 集群数据结构

clusterNode结构保存了一个节点的当前状态
         


    

1.3 CLUSTER MEET 命令的实现

        CLUSTER MEET <ip> <port>

通过向节点A发送命令,客户端可以让接收命令的节点A将另一个节 点B添加到节点A当前所在的集群里面
            1.节点A会为节点B创建一个clusterNode结构,并将该结构添加到自己的 clusterState. nodes 字典里面
            2.节点A将根据CLUSTER MEET 命令给定的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消息,握手完成

 

二、槽指派

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

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

2.1 记录节点的槽指派信息

clusterNode :
        slots[16384/8]
            数组的长度为16384/8=2048个字 节,每个字节上可以对应8个槽
            如果slots数组在索引i上的二进制位的值为1,那么表示节点负责处理槽i
            如果slots数组在索引i上的二进制位的值为0,那么表示节点不负责处理槽i
        numslots
            记录节点负责处理的槽的数量,也即是slots数组中值为1的 二进制位的数量

 

2.2 传播节点的槽指派信息

一个节点除了会将自己负责处理的槽记录在clusterNode结构的slots属性和 numslots属性之外,它还会将自己的slots数组 通过消息发送给集群中的其他节点,以此来告知其他 节点自己目前负责处理哪些槽
    集群中的每个节点都会将自己的slots数组通过消息发送给集群中的其他节点, 并且每个接收到slots数组的节点都会将数组保存到相应节点的clusterNode结构里面, 因此,集群中的每个节点都会知道数据库中的16384个槽分别被指派给了集群中的哪些节点
 

 

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

 clusterStat
        clusterNode *slots[16384];
            结构中的slots数组记录了集群中所有16384个槽的指派信息
        如果slots [幻指针指向NULL,那么表示槽i尚未指派给任何节点
        如果slots [i]指针指向一个clusterNode结构,那么表示槽i已经指派给了 clusterNode结构所代表的节点
        高效地解决一个节点是否被指派的问题,时间复杂度为O(1)

2.4 CLUSTER ADDSLOTS 命令的实现

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

CLUSTER ADDSLOTS <slot> [slot …]

CLUSTER ADDSLOTS 1 2

 

三、在集群中执行命令

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

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

     1.如果键所在的槽正好就指派给了当前节点,那么节点直接执行这个命令。

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

 

3.1 计算键属于哪个槽

 CRC16(key) & 16383
        CRC16 (key) 语句用于计算键key的CRC-16校验和
        & 16383 语句则用于 计算出一个介于0至16383之间的整数作为键key的槽号

slot = slot_number(key):计算槽号

 reply_client(slot) :将槽号返回给客户端


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

当节点计算出键所属的槽i之后,节点就会检查自己在clusterState . slots数组 中的项i,判断键所在的槽是否由自己负责:

    如果 clusterState . slots [i]等于 clusterState .myself,那么说明槽 i 由当前节点负责,节点可以执行客户端发送的命令
    如果 clusterState . slots [i]不等于 clusterState .myself,那么说明槽 i并非由当前节点负责,节点会根据clusterState.slots[i]指向的clusterNode 结构所记录的节点IP和端口号,向客户端返回MOVED错误,指引客户端转向至正在处理槽 i的节点

 

3.3 MOVED错误

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

MOVED错误的格式为:MOVED <slot> <ip>:<port>

MOVED 10086 127.0.0.1:7002

    当客户端接收到节点返回的MOVED错误时,客户端会根据MOVED错误中提供的IP地 址和端口号,转向至负责处理槽slot的节点,并向该节点重新发送之前想要执行的命令

3.4 节点数据库的实现

节点和单机服务器在数据库方面的一个区别是,节点只能使用0号数据库,而单机 Redis服务器则没有这一限制。

除了将键值对保存在数据库里面之外,节点还会用clusterState结构中的 slots_to_keys跳跃表来保存槽和键之间的关系

slots_to_keys跳跃表每个节点的分值(score)都是一个槽号,而每个节点的成员 (member )都是一个数据库键:
        每当节点往数据库中添加一个新的键值对时,节点就会将这个键以及键的槽号关联 到 slots_to_keys 跳跃表
        当节点删除数据库中的某个键值对时,节点就会在slots_to_keys跳跃表解除被 删除键与槽号的关联

 

四、重新分片

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

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

4.1 重新分片的实现原理

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

redis-trib对集群的单个槽slot进行重新分片的步骤如下:

1. redis-trib 对目标节点发送 CXUSTEJR SCTSiOT <slot> IMPORTING <source_id>命令,让目标节点准备好从源节点导人(import)属于槽slot的键值对

2. redis-trib对源节点发送CLUSTER SETSLOT <slot> MIGRATING <target_id>命令,让源节点准备好将属于槽slot的键值对迁移(migrate )至目标节点

3.redis-trib 向源节点发送 CLUSTEK GETKEYSINSLOT <slot> <count>命令, 获得最多count个属于槽slot的键值对的键名(key name )

4. 对于步骤3获得的每个键名,redis-trib都向源节点发送一个 MIGRATE<target_ip> <target_port> <key_name> 0 <timeout〉命令,将被选中的键原 子地从源节点迁移至目标节点

5.重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对都被迁移 至目标节点为止

6.redis-trib 向集群中的任意一个节点发送 CLUSTER SETSLOT <slot>NODE<target_id>命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群, 最终集群中的所有节点都会知道槽slot已经指派给了目标节点

 

五、ASK 错误

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

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

 

5.1 CLUSTER SETSLOT IMPORTING 命令的实现

    clusterState结构的importing_slots_f rom数组记录了当前节点正在从其他 节点导人的槽
    importing_slots_f rom [i]的值不为 NULL,而是指向一个 clusterNode 结构,那么表示当前节点正在从clusterNode所代表的节点导人槽i

5.2 CLUSTER SETSLOT MIGRATING 命令的实现

    clusterState结构的migrating_slots_to数组记录了当前节点正在迁移至其他 节点的槽
    如果migrating—slots一to [i]的值不为NULL,而是指向一个clusterNode结构, 那么表示当前节点正在将槽i迁移至clusterNode所代表的节点

5.3 ASK 错误

 如果节点收到一个关于键key的命令请求,并且键key所属的槽i正好就指派给了这 个节点,那么节点会尝试在自己的数据库里查找键key,如果找到了的话,节点就直接执行 客户端发送的命令
    如果节点没有在自己的数据库里找到键key,那么节点会检査自己的 clusterState .migrating_slots_to [i],看键key所属的槽i是否正在进行迁移, 如果槽i的确在进行迁移的话,那么节点会向客户端发送一个ASK错误,引导客户端到正 在导入槽i的节点去査找键key

5.4 ASKING 命令

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


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

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

5.5 ASK错误和MOVED错误的区别

 MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到 关于槽i的MOVED错误之后,客户端每次遇到关于槽i的命令请求时,都可以直 接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点

 ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施:在客户 端收到关于槽i的ASK错误之后,客户端只会在接下来的一次命令请求中将关于槽 i的命令请求发送至ASK错误所指示的节点,但这种转向不会对客户端今后发送关 于槽i的命令请求产生任何影响,客户端仍然会将关于槽i的命令请求发送至目前 负责处理槽i的节点,除非ASK错误再次出现

 

六、复制与故障转移

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

6.1 设置从节点

 CLUSTER REPLICATE <node_id>

可以让接收命令的节点成为node_id所指定节点的从节点,并开始对主节点进行复制
        1.接收到该命令的节点首先会在自己的clusterState . nodes字典中找到node_id 所对应节点的clusterNode结构,并将自己的clusterState .myself • slaveof 指针指向这个结构,以此来记录这个节点正在复制的主节点
        2..然后节点会修改自己在clusterState.myself.flags中的属性,关闭原本的 REDIS_NODE_MASTER标识,打开REDIS_NODE_SLAVE标识,表示这个节点已 经由原来的主节点变成了从节点
        3.节点会调用复制代码,并根据clusterState.myself.slaveof指向的 clusterNode结构所保存的IP地址和端口号,对主节点进行复制。因为节点的复 制功能和单机Redis服务器的复制功能使用了相同的代码,所以让从节点复制主节 点相当于向从节点发送命令SLAVEOF <master_ip> <master_port>

一个节点成为从节点,并开始复制某个主节点这一信息会通过消息发送给集群中的其他 节点,最终集群中的所有节点都会知道某个从节点正在复制某个主节点
   clusterNode
            slaves属性
                正在复制这个主节点的从节点数量
            numslaves[] 属性
                每个数组项指向一个正在复制这个主节点的从节点的clusterNode结构 


6.2故障检测

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

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

6.3故障转移

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

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


6.4 选举新的主节点

新的主节点是通过选举产生的。

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

七、消息

集群中的各个节点通过发送和接收消息(message)来进行 通信,我们称发送消息的节点为发送者(sender),接收消息的 节点为接收者。

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

MEET消息:
        当发送者接到客户端发送的CLUSTERMEET命令时,发送者会向接收者发送MEET消息,请求接收者加人到发送者当前 所处的集群里面

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

PONG消息:
        当接收者收到发送者发来的MEET消息或者PING消息时,为了向发送 者确认这条MEET消息或者PING消息已到达,接收者会向发送者返回一条PONG 消息。另外,一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他 节点立即刷新关于这个节点的认识,例如当一次故障转移操作成功执行之后,新的主节点会向集群广播一条PONG消息,以此来让集群中的其他节点立即知道这个节 点已经变成了主节点,并且接管了已下线节点负责的槽

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

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

7.1 消息头

节点发送的所有消息都由一个消息头包裹,消息头除了包含消息正文之外,还记录了消 息发送者自身的一些信息

7.2 MEET、PING、PONG 消息的实现

 

7.3 FAIL消息的实现

 

7.4 PUBLISH消息的实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值