Redis集群-cluster底层原理及实现

文章目录

  • 1. 集群
    • 1.1 节点
      • 1.1.1 启动节点
      • 1.1.2 集群数据结构
      • 1.1.3 CLUSTER MEET命令的实现
    • 1.2 槽指派
      • 1.2.1 记录节点的槽指派信息
      • 1.2.2 传播节点的槽指派信息
      • 1.2.3 记录集群所有槽的指派信息
    • 1.3 在集群中执行命令
    • 1.4 重新分片
    • 1.5 ASK错误
    • 1.6 复制与故障转移
      • 1.6.2 故障检测
      • 1.6.3 选举新的主节点(简单说一下)
    • 1.7 消息

1. 集群

  • 定义

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

1.1 节点

一个Redis集群由多个节点组成。要组件一个真正可工作的集群,必须将各个独立的节点连接起来,构成一个包含多个节点的集群。

1
CLUSTER MEET <ip><port>

向一个node发送CLUSTER MEET命令,会让node节点与ip和port所指定的节点进行握手。所指定节点会响应握手,握手成功后,node节点就会将握手的节点添加到node节点当前所在的集群中。

在这里插入图片描述

1.1.1 启动节点

节点本质上就是一个运行在集群模式下的Redis服务器,Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。
在这里插入图片描述

1.1.2 集群数据结构

  • 每个节点会使用一个clusterNode结构来记录自己的状态,并为集群中所有其他节点都创建一个相应的clusterNode结构,来记录其他节点状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct clusterNode{
    // 创建节点时间
    mstime_t ctime;
    //节点的名字,由40个十六进制字符组成
    char name[REDIS_CLUSTER_NAMELEN];
    // 节点标识
    // 使用各种不同的标识值记录节点的角色(如主节点、从节点)
    // 以及节点当前状态(如在线或下线)
    int flags;
    // 节点当前的配置纪元,用于实现故障转移
    uint64_t configEpoch;
    // 节点IP
    char ip[REDIS_IP_STR_LEN];
    // 节点端口号
    int port;
    // 保存连接节点所需有关信息
    clusterLink *link;
    //...
};

其中link属性是一个clusterLink结构,保存了连接节点所需的有关信息,比如连接创建的时间,TCP套接字描述符,输入缓冲区(保存着从其他节点收到的消息),输出缓冲区(保存着等待发送给其他节点的消息)和与这个连接相关联的节点。

redisClient结构和clusterLink结构的区别

二者的区别在于redisClient结构中的套接字和缓冲区是用于连接客户端的,而clusterLink结构中的套接字和缓冲区是用于连接节点的。

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

在这里插入图片描述

1.1.3 CLUSTER MEET命令的实现

我们回过头来看一下CLUSTER MEET命令具体做了些什么。握手过程:

1)节点A为节点B创建一个clusterNode结构,并将它添加到自己的clusterState.node字典里

2)节点A根据命令中给定的IP和端口号,向节点B发送MEET消息

3) B收到消息,B为A创建一个clusterNode结构,并将它添加到自己的clusterState.node字典里

4)B向A返回一条PONG消息,让A知道,我收到你的MEET消息啦。

5) A收到B的PONG

6)A向B返回一条PING,让B知道,我收到你的PONG啦~

7)B收到PING,握手完成

在这里插入图片描述

1.2 槽指派

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

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

CLUSTER ADDSLOTS命令,将一个或多个槽指派给节点负责

1
CLUSTER ADDSLOTS <slot> [slot ...]

1.2.1 记录节点的槽指派信息

在这里插入图片描述

用数组存,长度为16384/8=2048个字节

1.2.2 传播节点的槽指派信息

在这里插入图片描述

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

在这里插入图片描述

1.3 在集群中执行命令

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

在这里插入图片描述

这里声明一下图中的MOVED错误一般是被“隐藏的”

集群模式的redis-cli客户端在接收到MOVED错误时,并不会打印出MOVED错误,而是根据MOVED错误自动进行节点转向,并打印出转向信息,所以我们是看不见节点返回的MOVED错误的。

但是单机模式下,会报错

1.4 重新分片

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

在这里插入图片描述
在这里插入图片描述

没用过的槽就直接指派,放了数据的就把数据迁移到目标节点。

1.5 ASK错误

? 在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现:属于被迁移槽的一部分键值对保存在源节点中,而另一部分键值对保存在目标节点中的情况。当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时,则返回ASK错误。

在这里插入图片描述

ASK错误和接到MOVED错误时情况类似,集群模式下不会打印错误而是根据提供的IP和端口进行转向动作。

1.6 复制与故障转移

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

主要有以下几步:

  • 从节点被选中为新的主节点
  • 新主节点接管已下线的旧主节点负责处理的槽
  • 旧主节点的其他从节点改为复制新主节点

1.6.2 故障检测

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

然后集群中各个节点会通过互相发送消息来交换集群中各个节点的状态信息。如果一个集群中,半数以上负责处理槽的主节点都将某个主节点x报告为疑似下线,那么这个x将被标记为已下线(FAIL)。

1.6.3 选举新的主节点(简单说一下)

1)当从节点发现自己正在复制的主节点已下线时(FAIL),这个从节点就会向集群广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息并且有投票权的主节点向该从节点投票。

2) 只有正在负责处理槽的处于相同配置纪元的主节点具有投票权,且只有一次投票机会。如果还没投,就投给从节点。

3)当一个从节点收到大于等于N/2+1个支持票时,则当选新的主节点

4)否则重新选举,直到选出一个

基于Raft算法的领头选举方法实现

1.7 消息

集群中各个节点通过发送和接收消息来进行通信,节点发送的消息主要有5种:

  • MEET消息
  • PING消息
  • PONG消息
  • FAIL消息:集群广播节点下线的消息。
  • PUBLIST消息:集群广播

集群中的各个节点通过Gossip协议来交换各自关于不同节点的状态信息。(Gossip协议自行百度哈,这里就不展开了)

注:FAIL消息的实现不是基于Gossip协议的,因为Gossip协议消息通常需要一段时间才能传播至整个集群,而FAIL需要让所有节点立即知道某个节点已下线。

参考文献:
【1】redis的设计与实现(第2版) 黄健宏