写点什么

Redis 集群架构剖析 (1):认识 cluster

作者:非晓为骁
  • 2022 年 3 月 14 日
  • 本文字数:2562 字

    阅读完需:约 8 分钟

本文档主要是学习 redis cluster 的一下学习笔记和想法,通过这篇文章,希望你能了解 redis 的 cluster 是如何构建的,以及里面的数据结构是怎么样的。当然,也值得去思考 redis 的集群架构设计思路,这个其实对自己的分布式系统架构设计是由帮助的。


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


  • 如何启动一个集群节点呢?

  • 又如何创建一个集群呢?

  • 创建好的集群,通过什么记录集群信息,又记录哪些信息呢?

  • 一个新的节点又是如何加入一个集群的呢?



节点启动

一个集群由多个节点组成,为了形成一个集群,那这个节点启动的配置一定不一般。


通常我们启动 redis,不考虑集群的话,正常都是直接用默认配置,用 docker 或者直接二进制起。但是要注意的是,如果想要将该节点加入某个集群,那么配置文件中的cluster-enabled必须设置为 true,否则无法加入任意一个集群。启动时的内部逻辑大致如下:



创建集群

从上一节我们得知一个 Redis 集群由多个节点(node)组成。一开始的时候,每个节点都是独立的,都处于一个只包含自己的集群当中。那么为了建立一个集群,那就需要把他们都连接起来。


连接节点的指令,可以通过CLUSTER MEET指令:


CLUSTER MEET <ip> <port>
复制代码


向一个节点 node 发送CLUSTER MEET指令,目的是为了让 node 和指定的 IP/PORT node 握手。当握手成功时,node 节点就会把对应的节点加到当前所在集群中。


下图用 3 个 redis 节点作为例子,端口分别是 6370、6371 和 6372,均在同一台服务器上部署(为了测试)



注意,这是一个节点一个节点添加到集群内的,也可以通过redis-cli create cluster指令来创建集群,可以参考另一篇 redis 的集群搭建,会详细介绍:Redis集群docker部署 - 知乎 (zhihu.com)

集群数据结构

了解了集群节点的启动和创建,那集群的信息是怎么存的呢?


在集群模式下会用到的数据,节点会将它们存在cluster.h/clusterNode结构、cluster.h/clusterLink结构,以及cluster.h/clusterState结构里面。

clusterNode

集群中的我是谁,我有什么证明


clusterNode 结构保存了一个节点的当前状态,比如节点的创建时间、节点的名字、节点当前的配置纪元、节点的 IP 地址和端口号等等。


每个节点都会使用一个 clusterNode 结构来记录自己的状态,并为集群中的所有其他节点(包括主节点和从节点)都创建一个相应的 clusterNode 结构,以此来记录其他节点的状态。


struct clusterNode{        // 创建节点的时间    mstime_t ctime;        // 节点的名字,由40个十六进制字符组成    char name[REDIS_CLUSTER_NAMELEN];        // 节点标识    // 使用各种不同的标识值记录节点的角色(比如主节点或者从节点,或者上线下线)    int flags;        // 节点当前的配置纪元,用于实现故障转移    uint64_ configEpoch;        // 节点的IP地址    char ip[REDIS_IP_STR_LEN];        // 节点的端口    int port;        // 保存连接节点的有关信息    clusterLink *link;        //...}
复制代码
clusterLink

如果集群有一个节点想要访问我,怎么可以快速访问到我


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


typedef struct clusterLink{    // 连接的创建时间    mstime_t ctime;        // TCP 套接字描述符    int fd;        // 输出缓冲区,保存着等待发送给其他节点的消息(message)    sds snduf;        // 输入缓冲区,保存着从其他节点收到的消息    sds rcvbuf;        // 与这个连接相关联的节点,如果没有的话就为NULL    struct clusterNode *node;} clusterLink
复制代码


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

clusterState

集群里面还有谁,我要记住你们


同时每个节点都会保存一个 clusterState 结构,这个结构记录了当前节点的视角下,集群目前所处的状态,好比说集群现在有多少个节点,当前是下线还是上线:


typedef struct clusterState {    // 指向当前节点的指针    clusterNode *myself;        // 集群当前的配置纪元,用于实现故障转移    uint64_t currentEpoch;        // 集群当前状态:是在线还是下线    int state;        // 集群中至少处理着一个槽的节点的数量    int size            // 集群节点名单(包括mysql节点)    // 字典的键为节点的名字,字典的值为节点对应的cluserNode结构    dict *nodes;        // ...} clusterState;
复制代码


redis 的集群数据结构其实是可以在自己的分布式架构借鉴的,当前节点信息,别人怎么联系我,当前集群拓扑,这三个数据结构里面存的东西就可以表示一个集群当前的节点状态信息。



节点加入集群

我们在第二节讲了用CLUSTER MEET这个指令来创建集群,在了解了集群数据结构之后,再来详细地剖析下,这个指令到底做了什么,上一节讲的数据结构又会填入哪些值,握手又是如何完成的。


假设现在 6370 节点对 6371 节点发起CLUSTER MEET指令:


  1. 6370 会创建一个clusterNode,并将这个clusterNode加到clusterState

  2. 6370 根据CLUSTER MEET的 IP 和 PORT,向 6371 节点发送一个MEET消息

  3. 6371 如果收到MEET消息,就会创建一个 6370 节点的clusterNode,并加到自己的clusterState里面

  4. 然后 6371 会向 6370 发送一个PONG

  5. 6370 收到PONG后,就认为 6731 收到MEET消息

  6. 之后 6370 会再向 6371 发送PING

  7. 6371 收到 6370 的PING之后,认为 6370 收到自己的PONG,此时握手完成

  8. 6370 会将 6371 的节点信息通过 Goosip 协议传播给集群的其他节点,让其他节点也去和 6371 握手。过一段时间之后,集群的其他节点就都会认识 6371



集群建好了

在前几节的步骤之下,集群已经创建好了。然后我们通过 6370 的视角,看一下现在的集群,根据第三节的数据结构,可以看到集群数据结构存的内容如下:



nodes 里面是 kv,key 是 node 的名称(正常不是这样的),value 就是对应的节点的 clusterNode。如果是别的节点视角,那对应的就是 myself 节点改一下。


注意到的是clusterState数据结构里面的一个字段 state,还是 REDIS_CLUSTER_FAIL。这意味着,我们现在创建好了集群,但是这个集群状态不可用。为什么不可用呢?我们知道的是 redis 是一个 kv 数据库,要存的东西的,那谁存什么值我们似乎还没有分配好,这个会不会是集群不可用的原因呢?请待下回分解。

用户头像

非晓为骁

关注

no pain no gain 2019.04.10 加入

用我的勤奋,一点一点地努力,提升自己的能力,拓展自己的视野,提高自己的认知。 我的知乎:https://www.zhihu.com/people/zhengfke

评论

发布
暂无评论
Redis集群架构剖析(1):认识cluster_redis_非晓为骁_InfoQ写作平台