Redis使用及源码剖析-21.Redis集群-2021-2-4

2022-02-22 13:48:14 浏览数 (1)

文章目录

  • 前言
  • 一、节点
    • 1.节点启动
    • 2.集群数据结构
    • 3.CLUSTER MEET 命令的实现
  • 二、槽指派
    • 1.记录节点的槽指派信息
    • 2.传播节点的槽指派信息
    • 3.记录集群中所有槽的指派信息
  • 三、在集群中执行命令
  • 四、重新分片
  • 总结

前言

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

一、节点

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

代码语言:javascript复制
CLUSTER MEET <ip> <port>

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

1.节点启动

一个节点就是一个运行在集群模式下的 Redis 服务器, Redis 服务器在启动时会根据 cluster-enabled 配置选项的是否为 yes 来决定是否开启服务器的集群模式。节点(运行在集群模式下的 Redis 服务器)会继续使用所有在单机模式中使用的服务器组件,除此之外, 节点会继续使用 redisServer 结构来保存服务器的状态, 使用 redisClient 结构来保存客户端的状态, 至于那些只有在集群模式下才会用到的数据, 节点将它们保存到了 cluster.h/clusterNode 结构, cluster.h/clusterLink 结构, 以及 cluster.h/clusterState 结构里面, 接下来的一节将对这三种数据结构进行介绍。

2.集群数据结构

clusterNode 结构保存了一个节点的当前状态, 比如节点的创建时间, 节点的名字, 节点当前的配置纪元, 节点的 IP 和地址, 等等。每个节点都会使用一个 clusterNode 结构来记录自己的状态, 并为集群中的所有其他节点(包括主节点和从节点)都创建一个相应的 clusterNode 结构, 以此来记录其他节点的状态:

代码语言:javascript复制
struct clusterNode {

    // 创建节点的时间
    mstime_t ctime;

    // 节点的名字,由 40 个十六进制字符组成
    // 例如 68eef66df23420a5862208ef5b1a7005b806f2ff
    char name[REDIS_CLUSTER_NAMELEN];

    // 节点标识
    // 使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
    // 以及节点目前所处的状态(比如在线或者下线)。
    int flags;

    // 节点当前的配置纪元,用于实现故障转移
    uint64_t configEpoch;

    // 节点的 IP 地址
    char ip[REDIS_IP_STR_LEN];

    // 节点的端口号
    int port;

    // 保存连接节点所需的有关信息
    clusterLink *link;

    // ...

};

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

代码语言:javascript复制
typedef struct clusterLink {

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

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

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

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

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

} clusterLink;

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

代码语言:javascript复制
typedef struct clusterState {

    // 指向当前节点的指针
    clusterNode *myself;

    // 集群当前的配置纪元,用于实现故障转移
    uint64_t currentEpoch;

    // 集群当前的状态:是在线还是下线
    int state;

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

    // 集群节点名单(包括 myself 节点)
    // 字典的键为节点的名字,字典的值为节点对应的 clusterNode 结构
    dict *nodes;

    // ...

} clusterState;

clusterState 结构是示意图如下所示:

3.CLUSTER MEET 命令的实现

通过向节点 A 发送 CLUSTER MEET 命令, 客户端可以让接收命令的节点 A 将另一个节点 B 添加到节点 A 当前所在的集群里面。收到命令的节点 A 将与节点 B 进行握手(handshake), 以此来确认彼此的存在, 并为将来的进一步通信打好基础: a.节点 A 会为节点 B 创建一个 clusterNode 结构, 并将该结构添加到自己的 clusterState.nodes 字典里面。 b.之后, 节点 A 将根据 CLUSTER MEET 命令给定的 IP 地址和端口号, 向节点 B 发送一条 MEET 消息(message)。 c.如果一切顺利, 节点 B 将接收到节点 A 发送的 MEET 消息, 节点 B 会为节点 A 创建一个 clusterNode 结构, 并将该结构添加到自己的 clusterState.nodes 字典里面。 d.之后, 节点 B 将向节点 A 返回一条 PONG 消息。 e.如果一切顺利, 节点 A 将接收到节点 B 返回的 PONG 消息, 通过这条 PONG 消息节点 A 可以知道节点 B 已经成功地接收到了自己发送的 MEET 消息。 f.之后, 节点 A 将向节点 B 返回一条 PING 消息。 g.如果一切顺利, 节点 B 将接收到节点 A 返回的 PING 消息, 通过这条 PING 消息节点 B 可以知道节点 A 已经成功地接收到了自己返回的 PONG 消息, 握手完成。

二、槽指派

Redis集群通过分片的方式来保存数据库中的键值对,整个数据库会被分为16384个槽。当所有的槽都有集群节点在处理时,集群处于上线状态,否则处于下线状态。通过向某个集群节点发送cluster addslots命令可以将槽分配给该节点,如下所示:

代码语言:javascript复制
redis>cluster addslots 0 1 2 ......5000 //将槽0-5000分配给客户端连接的服务器节点

下面简要介绍cluster addslots命令会执行的操作。

1.记录节点的槽指派信息

clusterNode的slot和numslot属性记录了节点处理那些槽,如下所示:

代码语言:javascript复制
struct clusterNode {
	// 由这个节点负责处理的槽
    // 一共有 REDIS_CLUSTER_SLOTS / 8 个字节长
    // 每个字节的每个位记录了一个槽的保存状态
    // 位的值为 1 表示槽正由本节点处理,值为 0 则表示槽并非本节点处理
    // 比如 slots[0] 的第一个位保存了槽 0 的保存情况
    // slots[0] 的第二个位保存了槽 1 的保存情况,以此类推
    unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */

    // 该节点负责处理的槽数量
    int numslots;   /* Number of slots handled by this node */
};

slots是一个大小为16384/8字节长的二进制位数组,其中某一位代表一个槽,若该位是1,表示这个槽由本节点处理。

2.传播节点的槽指派信息

槽指派以后节点除了记录自己负责的槽以外,还会降slots数组信息发送给集群中的其他节点,其他节点收到信息后会在clusterState 的nodes词典中,找到发送信息节点对应的clusterNode结构,并更新该结构的slots数组。

3.记录集群中所有槽的指派信息

clusterState 的slots成员记录了所有槽指派给了那个节点,如下所示:

代码语言:javascript复制
typedef struct clusterState {

   clusterNode *slots[16384]

} clusterState;

若slots[i]=NULL,表示槽i未被指派,否则表示已经指向了某个节点,示意图如下所示:

三、在集群中执行命令

当所有槽都指派给节点后,集群进入上线状态,此时客户端就可以向服务端发送命令了。当客户端向服务器发送键有关的命令时,接收命令的节点就会计算键属于哪个槽并将判断槽是否是自己负责,若是则执行命令,不是则向客户端返回moved错误,引导客户端指向正确节点。如下所示:

代码语言:javascript复制
127.0.0.1:7001>set msg hello
127.0.0.1:7002>get msg
->redirected to slot[6257] located at 127.0.0.1:7001 //moved错误
ok
127.0.0.1:7001>

四、重新分片

重新分片可以将指定数目的分配给某个节点的槽分配给另一个节点,重新分片可以在线进行,由redis集群管理软件redis-trib负责执行,在此不多做介绍。

五、复制和故障转移

集群中的节点分为主节点和从节点,主节点负责处理槽,从节点负责复制主节点。当主节点下线时,从节点会代替主节点处理命令请求。

总结

本文对redis集群进行了简要介绍,方便对集群进行一个简单的初步了解。

0 人点赞