Redis(八):Redis集群

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

1、节点

2、槽指派

3、集群中执行命令

4、重新分片

5、ASK错误

6、复制和故障转移

7、Redis槽数量为什么为16384


1、节点

1.1 启动节点

    一个节点就是一个运行在集群下的Redis服务器,Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。运行在集群模式下的节点会继续使用所有在单机模式中使用的服务器组件。只有在集群模式下才会用到的数据,节点将它们保存到了clusterNodeclusterLinkclusterState结构里面。

1.2 多个节点组建集群

    在刚开始的时候,每个节点都是相互独立的,它们都处在一个只包含自己的集群当中,要组件一个真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。连接各个节点的工作可以使用CLUSER MEET命令来完成,该命令的格式如下:

CLUSER MEET <ip> <port>

    向一个节点发送这个命令时,可以让node节点与ip和port指定的节点进行握手,当握手成功时,node节点就会将ip和port所指定的节点添加到node节点当前所在的集群中。 redis集群的数量最大为槽slot的数量,即16384个节点。

1.3 集群的数据结构

  • clusterNode:保存了一个节点的当前状态,比如节点的创建时间、节点的名字、节点当前的配置纪元、节点的IP地址和端口号等。每个节点都会使用这个数据结构来保存自己的状态,并为集群中的其他节点都创建一个相应的数据结构来记录其他节点的状态。
  • clusterLink:保存了连接节点所需的有关信息,比如套接字描述符,输入缓冲区和输出缓冲区。
  • clusterState:该结构记录了当前节点的视角下,集群目前所处的状态,如集群是在线还是下线,集群包含多少节点,集群当前的配置纪元等。

2、槽指派

    Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分成16384个槽(slot),数据库中每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或者最多16384个槽。当数据库的16384个槽都有节点在处理时,集群处于上线状态,相反,如果数据库中任何一个槽没有的到处理,那么集群处于下线状态。

    使用CLUSER MEET命令将多个节点连接到同一个集群里面后,这个集群仍然处于下线状态,因为集群中的三个节点都没有在处理任何槽。通过向节点发送CLUSER ADDSLOTS命令,我们可以将一个或者多个槽指派给节点负责:

CLUSER ADDSLOTS <slot> [slot ...]

    例如,执行以下命令可以将0-5000指派给节点7000负责:

127.0.0.1:7000> CLUSER ADDSLOTS 0 1 2 ... 5000

2.1 记录节点的槽指派信息

struct clusterNode 
{
    /* ... ... */
    unsigned char slots[16384/8];
    int numslots;
    /* ... ...  */
}
  • slots属性是一个二进制位数组,用于保存该节点处理哪些槽
  • numslots记录了节点负责处理的槽的数量,也即是slots数组中值为1的二进制位的数量。 

2.2 传播节点的槽指派信息

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

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

typedef struct clusterState
{
    /* ... ...  */
    clusterNode *slots[16384];
    /* ... ... */
} clusterState;

    clusterState中的slots数组记录了集群中所有16384个槽的指派信息。如果slots[i]指针为NULL,那么表示槽i尚未指派给任何节点;如果slots[i]指针指向一个clusterNode结构,那么表示槽i已经指派给了clusterNode结构所代表的节点。如果只将槽指派信息保存在各个节点的clusterNode.slots数组里面,会出现一些无法高效处理的问题,例如为了知道槽i是否已经被指派,或者槽i指派给了哪个节点,程序需要遍历clusterState.nodes字典中所有的clusterNode结构,检查这些结构的slots数组,这个过程负责度为O(N),而通过将所有槽指派信息保存在clusterState.slots数组里面,程序要检查槽i是否被指派或者取得负责处理槽i的节点,只需要访问clusterState.slots[i]的值即可,这个操作的复杂度为O(1)。

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

3、集群中执行命令

    数据库中的16384个槽都进行了指派之后,集群就进入了上线状态。当客户端向节点发送与数据库有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己。

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

3.1 计算键属于哪个槽    

CRC16(key) & 16383

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

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

    得到槽号i之后,如果clusterState.slots[i]指向的是自己的节点,则节点执行命令。如果不是,则根据clusterState.slots[i]指向的clusterNode记录的节点IP和端口号,向客户端返回MOVED错误,指引客户端转向至正在处理槽i的节点。

3.3 MOVED错误

    MOVED错误的格式为:

MOVED <slot> <ip> <port>
  • slot为键所在的槽
  • ip和port是负责处理槽slot的ip和端口号

    集群模式下,收到MOVED错误时不会打印出来,客户端会根据MOVED错误自己转向。 

4、重新分片

    Redis集群重新分片操作可以将任意数量已经指派给某个节点的槽改为指派给另一个节点,并且相关槽所属的键值对也会从源节点移动到目标节点。重新分片是由集群管理软件redis-trib负责执行的,redis提供了重新分片所需的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。具体步骤如下:

  • 1)redis-trib对目标节点发送命令,让目标节点准备好从源节点导入属于槽slot的键值对
  • 2)redis-trib对源节点发送命令,让源节点准备好将属于槽slot的键值对迁移到目标节点
  • 3)redis-trib对源节点发送命令,获得最多count个属于slot槽的键值对键名
  • 4)对于3)得到的键名,redis-trib都向源节点发送命令,将选中的键从源节点迁移到目标节点
  • 5)重复执行3和4步骤,直到源节点保存的所有属于槽slot的键值对都被迁移到目标节点为止。
  • 6)redis-trib向集群的任一节点发送命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群。

5、ASK错误

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

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

ASK错误和MOVED命令的区别:

  • MOVED错误会影响客户端今后的命令发送所指向的节点
  • ASK错误不会影响客户端今后发送的命令所指向的节点

6、复制和故障转移

6.1 设置从节点

    向一个节点发送命令:

CLUSTER REPLICATE <node-id>

   可以让接收命令的节点成为node-id所指定节点的从节点,并开始对主节点进行复制。 

6.2 故障检测

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

    在一个集群里面,集群的各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息,例如某个节点是处于在线状态、疑似下线状态还是已下线状态,如果半数以上处理槽的主节点都将某个主节点x报告为疑似下线,那么这个主节点x将被标记为已下线,将主节点x标记为已下线的节点会向集群广播一条关于主节点x已下线的消息,所有收到已下线消息的节点都会将主节点x标记为已下线。

6.3 故障转移

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

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

7、Redis槽数量为什么为16384

  • (1)如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。

    如在消息头中,最占空间的是myslots[CLUSTER_SLOTS/8]。当槽位为65536时,这块的大小是:65536÷8÷1024=8kb。因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。

  • (2)redis的集群主节点数量基本不可能超过1000个

    集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。

  • (3)槽位越小,节点少的情况下,压缩比高

    Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。ps:文件压缩率指的是,文件压缩前后的大小比。

参考:《Redis设计与实现》

猜你喜欢

转载自blog.csdn.net/MOU_IT/article/details/113820697