Redis(三十):集群(一)——集群的数据结构底层与建立连接过程

前面我们已经讲过Redis实现高可用式的一种方法是哨兵模式(Sentinel),但其实哨兵模式高可用其实也不算很好,哨兵模式针对的主服务器只有一台(因为要监视同一台主服务器的哨兵才会在一起形成网络),所以Redis又提供了另外一种实现高可用式的方案,称为集群

哨兵模式仅仅解决了一个切换问题,就是主服务器宕机了,就让从服务器担任新的主服务器,继续提供服务,在这期间,依然会有一个瞬断问题(就是切换期间,服务器是停止对外提供服务的)

那么如何解决这个瞬断问题呢?

Redis使用了集群,就是一种分布式数据库的方案,相当于对外提供服务的有多台主服务器,通过负载均衡的方式去分配外面的请求,此时如果一台主服务器挂掉(内部的主从再进行选举,选出新的主服务器),外面的请求可以让其他未挂掉的主服务器处理,这就解决了瞬断问题。

拓展

其实高可用性是相对的,无法完成百分百的高可用性,也就是无法保证对外服务不会出现丢失请求这种情况,比如一个请求进来集群,分配给到A服务器(此时A服务器没挂),处理请求的时候,A服务器挂了,这个请求也会丢失。

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

节点

一个Redis集群通常是由多个节点组成的(一个节点对应的就是一台服务器),在刚开始还没有集群的时候,一个节点都是独立的,都分别处于一个只包含自己的集群中,要组建一个高可用的集群,需要将各个独立的节点连接起来,构成一个拥有多节点的集群里面。

cluster nodes //查看当前节点集群的信息
cluster meet ip地址 端口号 //让指定ip地址和端口号的服务器加入到自己的集群

在这里插入图片描述
使用cluster meet 时,可以当前节点与指定节点进行握手,当握手成功时,当前节点就会将返回握手响应的节点加入到当前集群中去

启动节点

一个节点其实就是一个运行在集群模式下的Redis服务器,判定是集群模式还是单机模式(普通Redis服务器)是根据配置文件的cluster-enabled配置选项是否为Yes来判定的
在这里插入图片描述
在这里插入图片描述
所以节点其实就是运行在集群模式下的Redis服务器,虽然在集群模式下,但其实其功能跟单机模式下的Redis差不多,因为他会继续所用所有在单机模式中使用的服务器组件

  • 节点会继续使用文件事件处理器来处理命令请求和返回命令回复
  • 节点也会继续使用时间事件处理器去执行ServerCron函数,而ServerCron函数里面的内容发生了变化,与普通的Redis服务器不一样,他会专门去执行ClusterCron函数(ClusterCron函数包含了集群模式下需要执行的常规操作)。
  • 节点继续使用键空间保存键值对对象,键值对对象的类型仍然是前面提到的5种
  • 节点继续使用AOF和RDB持久化模块进行持久化
  • 节点继续使用发布与订阅模块
  • 节点继续使用复制模块(节点之间也有主从关系)
  • 节点会继续使用Lua脚本环境来执行客户端输入的Lua脚本

可以看到,普通Redis服务器有的功能,节点也会有

除此之外,像Sentinel一样,节点服务器也有属于节点状态结构(Sentinel有一个SentinelStat结构),ClusterNode、ClusterLink结构以及ClusterState结构里面

当然,服务器状态还是保存在RedisServer结构中

集群数据结构

clusterNode结构保存了一个节点的当前状态,比如节点的创建时间、节点的名字、节点当前的配置纪元(集群也有主从节点,也要进行故障转移)、节点的IP地址和端口号等等

当使用集群模式开启了服务器时,就代表这个服务器是一个节点,节点会用clusterNode结构来记录自己的状态,并为集群中的所有节点(主节点和从节点)都会去创建一个相应的clusterNode结构,用该结构去记录其他节点的状态

struct clusterNode(
	//创建节点时间
    mstime_t ctime;
    //节点的名字
    //由40个十六进制字符组成
    char name[REDIS_CLUSTER_NAMELEN];
    //节点标识
    //用来区分节点是主还是从节点
    //以及区分节点的状态(上线还是下线)
    int flags;
    //节点当前的配置纪元,用于实现故障转移
    unit64_t configEpoch;
    //节点的ip地址
    char ip[REDIS_IP_STR_LEN];
    //节点的端口号
    int port;
    //保存连接节点所需的有关信息(TCP建链连接)
    clusterLink *link;
    //...
)

前面提到过,ClusterNode结构之外,还有一个ClusterLink结构,从ClusterNode结构里面的属性看到,ClusterLink结构是被用在ClusterNode里面的,并用来保存连接节点所需的有关信息,比如套接字描述符,输入缓冲区和输出缓冲区

typedef struct clusterLink(
	//连接的创建时间
    mstime_t ctime;
    //TCP套接字描述符(记录节点连接当前节点使用的套接字)
    int fd;
    //输出缓冲区(保存着等待发送给其他节点的消息)
    sds sndbuf
    //输入缓冲区(保存着从其他节点接收到的信息)
    sds rcvbuf;
    //与这个连接相关联的节点,如果没有的话就为NULL(也就是引用clusterLink的节点)
    //相当于形成了一个互通
    struct clusterNode *node;
)clusterLink

拓展

RedisClient结构与ClusterLink结构的相同与不同之处

相同之处在于,两者都有记录自己的套接字描述符和输入输出缓冲区

不同之处在于,RedisClient的套接字和缓冲区是用来连接客户端的(服务器根据这些去得到请求客户端的信息,通过输入缓冲区和输出缓冲区进行命令提取和回复),而ClusterLink是用来连接节点的(当前节点通过这个节点的ClusterNode里面的ClusterLink里面的信息进行连接,获取发来的消息或者发送回复)

最后,每一个节点都保存着一个ClusterState结构(上面提到的ClusterNode都是保存在ClusterState俩面的),该结构记录了在当前节点的视角下,集群目前所处的状态,例如集群是下线的还是上线,集群包含多少个节点,集群当前的配置纪元

typedef struct clusterState(
	//指向当前节点的指针
    clusterNode *myself;
    //集群当前的配置纪元,用来实现故障转移的
    uinit64_t currentEpoch;
    //集群当前的状态,下线还是上线
    int state;
    //集群中处理槽的节点的数量
    int size;
    //集群节点名单(包括Myself)
    //使用字典保存,键为节点的名字,值为节点对应的clusterNode结构
    dict *nodes;
    //....
)clusterState;

总结起来,集群数据结构大概如下所示

每个节点都有ClusterState去保存自己和其他集群节点的状态(ClusterNode),在ClusterNode中又保存着节点之间的连接信息(当前节点与保存的节点)

在这里插入图片描述

CLUSTER MEET命令的实现

经过上面的学习,已经知道集群的数据结构是怎样的,下面就看看Cluster Meet命令是怎么实现的

CLUSTER MEET命令的作用是,执行Cluster命令的A节点会将命令中指定的B节点给添加到A节点的集群中去,跟邀请别人进群差不多

CLUSTER MEET <ip> <port>

节点A会先跟B进行握手,以此来确认彼此的存在,并未将来的进一步通信打好基础,下面就来看看握手的过程

  1. 节点A先为节点B创建一个ClusterNode结构,并将该结构添加到自己的ClusterState.nodes字典里面(现在还不知道节点B的名字,会以一个随机字符串来当节点B的名字,而且flags属性为REDIS_NODE_HANDSHAKE|REDIS_NODE_MEET状态,表明正在握手处理中而且等待MEET信息发送处理)
  2. 节点A根据命令里面给出的IP地址和PORT端口号,建立TCP连接,向节点B发送一条Meet信息(Meet信息是确认节点B是否存在,最后将REDIS_NODE_MEET标志清除,表明Meet信息已经发送了)
  3. 节点B顺利接收到节点A发送的Meet信息的话,节点B就会为节点A创建一个ClusterNode结构,并将该结构添加到自己的ClusterState.nodes字典里面**(MEET信息里面有节点A的port和ip地址,节点B根据这些信息去创建ClusterNode对象,但此时flag标志还是初始化为REDIS_NODE_HANDSHAKE,注意此时节点B也还是不知道节点A的名字)**
  4. 节点B添加完成之后,节点B会返回一条PONG信息(PONG信息里面还有节点B的名字)给节点A
  5. 节点A收到节点B返回的PONG信息,根据PONG信息可以知道节点B是从节点还是主节点,还有节点B的名字,根据这些信息去修改节点A的ClusterState.nodes字典里面的ClusterNode信息**(清除原来的REDIS_NODE_HANDSHAKE标记,然后根据主从信息,设置为REDIS_NODE_MASTER或者REDIS_NODE_SLAVE,然后将节点B的名字进行更新)**
  6. 节点A确认收到PONG信息之后,会返回一条PING信息给节点B
  7. 经过此步节点A与节点B就握手完成了
    在这里插入图片描述

连接细节拓展

前面提到过,ServerCron函数会执行ClusterCron函数(定时执行任务)

在ClusterCron函数会轮询当前节点的ClusterStat结构里面的nodes字典的每一个节点(ClusterNode结构),ClusterNode里面的ClusterLink为空(代表没连接)就表示尚未向该节点建立连接。

节点A新创建节点B的ClusterNode对象(此时ClusterNode的CluserLink为空,因为还没有进行连接),会被ServerCron函数给扫描到,然后进行TCP建链连接。

步骤如下

  • 调用anetTcpNonBlockBindConnect函数,开始向节点B发起非阻塞的TCP连接

  • 调用createClusterLink,创建ClusterLink结构

  • 给套接字绑定可读事件(回看客户端连接),绑定ClusterReadHandler处理器

  • ClusterReadHandle会判断ClusterNode的flag标识,判断发送MEET包、PONG包还是PING包

  • 如果发送成功,证明连接成功,如果发送失败,证明发送失败,直接释放该连接

  • 此时节点B收到发来的包,也会触发可读事件,交给ClusterReadHandler处理器去处理

  • 调用clusterProcessPacket函数处理该包,以发送者的名字去看看自己的ClusterStat里面的nodes中是否有对应的ClusterNode,如果没有,而且为MEET包,那么就代表是新的连接,为其创建一个ClustserNode对象,并且绑定上对应的ip和port,flag标志改为REDIS_NODE_SHAKEHAND,然后插入到nodes字典中**(这一步节点B是以NULL参数去设置节点A的ClusterNode结构的name属性的,只有在后续B与A握手,A发送PONG包,才会得到A的名字然后进行修改)**

到此连接就完成了,下面就是节点B发送PONG包了

要注意的是,A与B握手,B也要与A握手,A在建立TCP连接时候,节点B根据MEET信息新建了ClusterNode对象,也会被自己的ClusterCron函数扫描到,然后建立TCP连接,然后也是一样的握手过程

之后,节点A会将节点B的信息通过Gossip协议传播给集群中的其他节点,让其他节点也与节点B进行握手,最终,经过一段时间之后,节点B会被集群中的所有结点认识

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值