Redis源码剖析和注释(二十五)--- Redis Cluster 的通信流程深入剖析(载入配置文件、节点握手、分配槽)

Redis Cluster 通信流程深入剖析

1. Redis Cluster 介绍和搭建

请查看这篇博客:Redis Cluster 介绍与搭建

这篇博客会介绍Redis Cluster数据分区理论和一个三主三从集群的搭建。

Redis Cluster文件详细注释
本文会详细剖析搭建 Redis Cluster 的通信流程

2. Redis Cluster 和 Redis Sentinel

Redis 2.8之后正式提供了Redis Sentinel(哨兵)架构,而Redis Cluster(集群)是在Redis 3.0正式加入的功能。

Redis ClusterRedis Sentinel都可以搭建Redis多节点服务,而目的都是解决Redis主从复制的问题,但是他们还是有一些不同。

Redis主从复制可将主节点数据同步给从节点,从节点此时有两个作用:

  • 一旦主节点宕机,从节点作为主节点的备份可以随时顶上来。
  • 扩展主节点的读能力,分担主节点读压力。

但是,会出现以下问题:

  1. 一旦主节点宕机,从节点晋升成主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。
  2. 主节点的写能力或存储能力受到单机的限制。

Redis的解决方案:

  • Redis Sentinel旨在解决第一个问题,即使主节点宕机下线,Redis Sentinel可以自动完成故障检测和故障转移,并通知应用方,真正实现高可用性(HA)。
  • Redis Cluster则是Redis分布式的解决方案,解决后两个问题。当单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的。

关于Redis Sentinel的介绍和分析:

Redis Sentinel 介绍与部署

Redis Sentinel实现(上)(哨兵的执行过程和执行内容)

Redis Sentinel实现(下)(哨兵操作的深入剖析)

3. 搭建 Redis Cluster的通信流程深入剖析

Redis Cluster 介绍与搭建一文中介绍了搭建集群的流程,分为三步:

  • 准备节点
  • 节点握手
  • 分配槽位

我们就根据这个流程分析Redis Cluster的执行过程。

Redis Cluster文件详细注释

3.1 准备节点

我们首先要准备6个节点,并且准备号对应端口号的配置文件,在配置文件中,要打开cluster-enabled yes选项,表示该节点以集群模式打开。因为集群节点服务器可以看做一个普通的Redis服务器,因此,集群节点开启服务器的流程和普通的相似,只不过打开了一些关于集群的标识。

当我们执行这条命令时,就会执行主函数

sudo redis-server conf/redis-6379.conf

main()函数中,我们需要关注这几个函数:

  • loadServerConfig(configfile,options)载入配置文件。
    • 底层最终调用loadServerConfigFromString()函数,会解析到cluster-开头的集群的相关配置,并且保存到服务器的状态中。
  • initServer()初始化服务器。
    • 会为服务器设置时间事件的处理函数serverCron(),该函数会每间隔100ms执行一次集群的周期性函数clusterCron()
    • 之后会执行clusterInit(),来初始化server.cluster,这是一个clusterState类型的结构,保存的是集群的状态信息。
    • 接着在clusterInit()函数中,如果是第一次创建集群节点,会创建一个随机名字的节点并且会生成一个集群专有的配置文件。如果是重启之前的集群节点,会读取第一次创建的集群专有配置文件,创建与之前相同名字的集群节点。
  • verifyClusterConfigWithData()该函数在载入AOF文件或RDB文件后被调用,用来检查载入的数据是否正确和校验配置是否正确。
  • aeSetBeforeSleepProc()在进入事件循环之前,为服务器设置每次事件循环之前都要执行的一个函数beforeSleep(),该函数一开始就会执行集群的clusterBeforeSleep()函数。
  • aeMain()进入事件循环,一开始就会执行之前设置的beforeSleep()函数,之后就等待事件发生,处理就绪的事件。

以上就是主函数在开启集群节点时会执行到的主要代码。

在第二步初始化时,会创建一个clusterState类型的结构来保存当前节点视角下的集群状态。我们列出该结构体的代码:

typedef struct clusterState {
    clusterNode *myself;  /* This node */
    // 当前纪元
    uint64_t currentEpoch;
    // 集群的状态
    int state;            /* CLUSTER_OK, CLUSTER_FAIL, ... */
    // 集群中至少负责一个槽的主节点个数
    int size;             /* Num of master nodes with at least one slot */
    // 保存集群节点的字典,键是节点名字,值是clusterNode结构的指针
    dict *nodes;          /* Hash table of name -> clusterNode structures */
    // 防止重复添加节点的黑名单
    dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
    // 导入槽数据到目标节点,该数组记录这些节点
    clusterNode *migrating_slots_to[CLUSTER_SLOTS];
    // 导出槽数据到目标节点,该数组记录这些节点
    clusterNode *importing_slots_from[CLUSTER_SLOTS];
    // 槽和负责槽节点的映射
    clusterNode *slots[CLUSTER_SLOTS];
    // 槽映射到键的有序集合
    zskiplist *slots_to_keys;
    /* The following fields are used to take the slave state on elections. */
    // 之前或下一次选举的时间
    mstime_t failover_auth_time; /* Time of previous or next election. */
    // 节点获得支持的票数
    int failover_auth_count;    /* Number of votes received so far. */
    // 如果为真,表示本节点已经向其他节点发送了投票请求
    int failover_auth_sent;     /* True if we already asked for votes. */
    // 该从节点在当前请求中的排名
    int failover_auth_rank;     /* This slave rank for current auth request. */
    // 当前选举的纪元
    uint64_t failover_auth_epoch; /* Epoch of the current election. */
    // 从节点不能执行故障转移的原因
    int cant_failover_reason; 
    /* Manual failover state in common. */
    // 如果为0,表示没有正在进行手动的故障转移。否则表示手动故障转移的时间限制
    mstime_t mf_end;            
    /* Manual failover state of master. */
    // 执行手动孤战转移的从节点
    clusterNode *mf_slave;      /* Slave performing the manual failover. */
    /* Manual failover state of slave. */
    // 从节点记录手动故障转移时的主节点偏移量
    long long mf_master_offset; 
    // 非零值表示手动故障转移能开始
    int mf_can_start;           
    /* The followign fields are used by masters to take state on elections. */
    // 集群最近一次投票的纪元
    uint64_t lastVoteEpoch;     /* Epoch of the last vote granted. */
    // 调用clusterBeforeSleep()所做的一些事
    int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
    // 发送的字节数
    long long stats_bus_messages_sent;  /* Num of msg sent via cluster bus. */
    // 通过Cluster接收到的消息数量
    long long stats_bus_messages_received; /* Num of msg rcvd via cluster bus.*/
} clusterState;

初始化完当前集群状态后,会创建集群节点,执行的代码是这样的:

myself = server.cluster->myself = createClusterNode(NULL,CLUSTER_NODE_MYSELF|CLUSTER_NODE_MASTER);

首先myself是一个全局变量,定义在cluster.h中,它指向当前集群节点,server.cluster->myself是集群状态结构中指向当前集群节点的变量,createClusterNode()函数用来创建一个集群节点,并设置了两个标识,表明身份状态信息。

该函数会创建一个如下结构来描述集群节点。

typedef struct clusterNode {
    // 节点创建的时间
    mstime_t ctime; /* Node object creation time. */
    // 名字
    char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
    // 标识
    int flags;      /* CLUSTER_NODE_... */
    uint64_t configEpoch; /* Last configEpoch observed for this node */
    // 节点的槽位图
    unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
    // 当前节点复制槽的数量
    int numslots;   /* Number of slots handled by this node */
    // 从节点的数量
    int numslaves;  /* Number of slave nodes, if this is a master */
    // 从节点指针数组
    struct clusterNode **slaves; /* pointers to slave nodes */
    // 指向主节点,即使是从节点也可以为NULL
    struct clusterNode *slaveof; 
    // 最近一次发送PING的时间
    mstime_t ping_sent;      /* Unix time we sent latest ping */
    // 接收到PONG的时间
    mstime_t pong_received;  /* Unix time we received the pong */
    // 被设置为FAIL的下线时间
    mstime_t fail_time;      /* Unix time when FAIL flag was set */
    // 最近一次为从节点投票的时间
    mstime_t voted_time;     /* Last time we voted for a slave of this master */
    // 更新复制偏移量的时间
    mstime_t repl_offset_time;  /* Unix time we received offset for this node */
    // 孤立的主节点迁移的时间
    mstime_t orphaned_time;     /* Starting time of orphaned master condition */
    // 该节点已知的复制偏移量
    long long repl_offset;      /* Last known repl offset for this node. */
    // ip地址
    char ip[NET_IP_STR_LEN];  /* Latest known IP address of this node */
    // 节点端口号
    int port;                   /* Latest known port of this node */
    // 与该节点关联的连接对象
    clusterLink *link;          /* TCP/IP link with this node */
    // 保存下线报告的链表
    list *fail_reports;         /* List of nodes signaling this as failing */
} clusterNode;

初始化该结构时,会创建一个link为空的节点,该变量是clusterLink的指针,用来描述该节点与一个节点建立的连接。该结构定义如下:

typedef struct clusterLink {
    // 连接创建的时间
    mstime_t ctime;             /* Link creation time */
    // TCP连接的文件描述符
    int fd;                     /* TCP socket file descriptor */
    // 输出(发送)缓冲区
    sds sndbuf;                 /* Packet send buffer */
    // 输入(接收)缓冲区
    sds rcvbuf;                 /* Packet reception buffer */
    // 关联该连接的节点
    struct clusterNode *node;   /* Node related to this link if any, or NULL */
} clusterLink;

该结构用于集群两个节点之间相互发送消息。如果节点A发送MEET消息给节点B,那么节点A会创建一个clusterLink结构的连接,fd设置为连接后的套节字,node设置为节点B,最后将该clusterLink结构保存到节点B的link中。

3.2 节点握手

当我们创建好了6个节点时,需要通过节点握手来感知到到指定的进程。节点握手是指一批运行在集群模式的节点通过Gossip协议彼此通信。节点握手是集群彼此通信的第一步,可以详细分为这几个过程:

  • myself节点发送MEET消息给目标节点。
  • 目标节点处理MEET消息,并回复一个PONG消息给myself节点。
  • myself节点处理PONG消息,回复一个PING消息给目标节点。

这里只列出了握手阶段的通信过程,之后无论什么节点,都会每隔1s发送一个PING命令给随机筛选出的5个节点,以进行故障检测。

接下来会分别以myself节点目标节点的视角分别剖析这个握手的过程。

Redis Cluster文件详细注释

3.2.1 myself节点发送 MEET 消息

由客户端发起命令:cluster meet <ip> <port>

当节点接收到客户端的cluster meet命令后会调用对应的函数来处理命令,该命令的执行函数是clusterCommand()函数,该函数能够处理所有的cluster命令,因此我们列出处理meet选项的代码:

    // CLUSTER MEET <ip> <
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值