卡飞资源网

专业编程技术资源共享平台

Redis系列(六):Redis集群模式

前言

集群,即Redis Cluster,是Redis3.0开始引入的分布式存储方案。集群是由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点;只有主节点负责读写请求和集群信息的维护,从节点只进行主节点数据和状态信息的复制。


上图是Redis Cluster典型的架构图,集群中的每一个Redis节点都是互相连通的,客户端任意直连到集群中的任意一个节点,就可以对其他Redis节点进行读写操作。

集群的作用

集群的作用,主要可以归纳为以下两点:

  1. 数据分区:又称为数据分片,是集群最核心的功能!集群将数据分散到多个节点,一方面突破了Redis单机内存大小限制【单机内存大小受限制问题:如果单机内存太大,bgsave和bgrewriteaof的fork操作可能导致主线程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出,等等】,存储容量大大增加;另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。
  2. 高可用:集群支持主从复制和主节点的自动故障转移,当任一节点发生故障时,集群仍然可以对外提供服务。

集群的基本原理

Redis集群中内置了16384哈希槽。当客户端连接到Redis集群之后,会同时得到一份关于这个集群的配置信息,当客户端具体对某一个key值进行操作时,会计算出它的一个Hash值,然后把结果对16384求余数,这样每个key都会对应一个编号在0~16383之间的哈希槽,Redis会根据节点数量大致均等的将哈希槽映射到不同的节点。

【注】:这里仅对Redis Cluster的原理做一个简单的了解,接下来我们将从3个方面详细介绍,主要包括:数据的分区方案、通信机制、数据结构。

  • 集群的分区方式

数据分区有顺序分区、哈希分区等,其中哈希分区由于其天然的随机性,使用比较广泛。集群的分区方案便是哈希分区的一种。我们知道衡量数据分区方法好坏的标准最主要的是:① 数据分布是否均匀;② 增减节点对数据分布的影响

(1)方案一:哈希值 % 节点数

最容易想到的方案:哈希取余分区,计算key的hash值,然后对节点数量进行取余运算,从而决定数据映射到那个节点上。弊端:当新增或者删减节点时,节点数量发生变化,系统中所有的数据都需要重新计算映射关系,引发大规模的数据迁移。

(2)方案二:一致哈希算法分区

一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环,范围是[0 - 2^32-1],对于每一个数据,根据key计算hash值,确定数据在环上的位置,然后从此位置沿顺时针,找到第一台Redis服务器就是其映射到的服务器。与哈希取余分区相比,一致性哈希分区将增减节点的影响限制在相邻的节点。

弊端:当节点数量较少时,增加或删减节点,对单个节点的影响可能很大,造成数据的严重不平衡。

(3)方案三:带有虚拟节点的一致性哈希分区

该方案是在一致性哈希分区基础上,引入虚拟节点的概念。Redis集群使用的便是这个方案,其中虚拟节点称为槽(slot)。槽是介于数据和实际节点之间的虚拟概念,每个实际节点包含一定数量的槽,每个槽包含哈希值在一定范围内的数据。数据的映射关系由数据hash到实际节点变成了数据hash到槽再到实际节点。

在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽解耦了数据和实际节点之间的关系,增加和删减节点对系统的影响很小。在Redis集群,槽的数量是16384个。

上图总结了Redis集群将数据映射到实际节点的过程:

Step1:Redis对数据的特征值[一般是key]计算哈希值,使用的算法是CRC16;

Step2:根据哈希值,计算数据属于哪个槽slot;

Step3:根据槽和节点的映射关系,计算数据属于哪个节点。

  • 节点通信机制

集群作为一个整体工作,是离不开节点之间的通信的。

  1. 两个端口

在哨兵机制中,节点分为数据节点和哨兵节点:数据节点存储数据,哨兵节点实现额外的控制功能。在集群中,是没有数据节点和哨兵节点之分的,所有的节点都是存储数据,也都参与集群状态的维护。为此,集群中的每个节点都提供另外两个TCP端口:

① 普通端口:主要用于为客户端提供服务(与单机节点类似),在节点间数据迁移时也会使用该端口;

② 集群端口:端口号是普通端口+10000(10000是固定值,无法改变)。集群端口只用于节点之间的通信,比如:搭建集群、增减节点、故障转移等操作时节点之间的通信

【注】:要特别注意两点:① 不要使用客户端连接集群端口;② 为了保证集群可以正常工作,在配置防火墙时,要同时开启普通端口和集群端口。

  1. Gossip协议

节点之间通信,按照通信协议可以分为几种类型:单对单、广播、Gossip协议等。

广播是指向集群内所有节点发送消息;优点:集群的收敛速度快(集群收敛是指集群内所有节点获得集群信息是一致的);缺点:每条消息都要发送给所有的节点,CUP、宽带等消耗都比较大。

Gossip协议:在节点数量有限的网络中,每个节点都“随机”的与部分节点通信(并不是真正的随机,而是根据特定的规则选择通信的节点);经过通信,每个节点的状态很快达到一致。优点:负载低、去中心化、容错性高(因为通信有冗余)等;缺点:集群的收敛速度慢。

  1. 消息类型

集群中的节点采用固定频率(每秒10次)的定时任务进行通信工作:判断是否需要发送消息及消息类型、确定接收节点、发送消息等。如果集群状态发生了改变,比如:增减节点、槽状态变更,通过节点之间的通信,所有节点会很快得知整个集群的状态,是集群收敛。

点之间发送的消息主要分为五种:MEET消息PING消息PONG消息FAIL消息PUBLISH消息。不同的消息类型,通信协议、发送的频率和时机、接收节点的选择等是不同的。

MEET消息:在节点握手阶段,当节点收到客户端的CLUSTER MEET命令时,会向新加入的节点发送MEET消息,请求新节点加入到当前集群;新节点收到MEET消息后会回复一个PONG消息;

PING消息:集群中每个节点每秒钟会选择部分节点发送PING消息,接收者收到消息后会回复一个PONG消息。PING消息的内容是自身节点和部分其他节点的状态信息,作用是彼此交换信息,以检测节点是否在线。PING消息使用Gossip协议发送,接收节点的选择兼顾了收敛速度和带宽成本,具体规则:一、随机找5个节点,在其中选择最久没有通信的1个节点 ;二、扫描节点列表,选择最近一次收到PONG消息时间大于cluster_node_timeout / 2 的所有节点,防止这些节点长时间未更新;

PONG消息:PONG消息封装了自身状态数据。可以分为两种:一、在接收到MEET/PING消息后回复的POGN消息; 二、节点向集群广播PONG消息,这样其他节点可以获知该节点的最新信息,比如:故障恢复后新的节点会广播PONG消息;

FAIL消息:当一个主节点判断另一个主节点进入FAIL状态时,会向集群广播这一FAIL消息;接收节点会将这一FAIL消息保存起来,便于后续的判断;

PUBLISH消息:节点接收到PUBLISH命令后,会执行该命令,然后向集群广播这一消息,接收节点也会执行PUBLISH命令。

  1. 数据结构

节点需要专门的数据结构来存储集群的状态。这里所谓集群的状态,是一个宽泛的概念,包括:集群是否处于上线状态、集群中有哪些节点、节点是否可达、节点的主从状态、槽的分布等等。

节点为了存储集群状态而提供的数据结构中,最关键的是clusterNodeclusterState结构:前者记录了一个节点的状态,后者记录了集群作为一个整体的状态。

① clusterNode结构

clusterNode结构保存了一个节点的当前状态,包括创建时间、节点id、ip和端口号等。每个节点都会用一个clusterNode结构记录自己的状态,并为集群内所有其他节点都创建一个clusterNode结构来记录节点状态。

typedef struct clusterNode {
    // 节点创建时间 
    mstime_t ctime; 
    // 节点id 
    char name[REDIS_CLUSTER_NAMELEN];
    // 节点的ip和端口号 
    char ip[REDIS_IP_STR_LEN]; 
    int port; 
    // 节点标识:整型,每个bit都代表了不同状态,如节点的主从状态、是否在线、是否在握手等 
    int flags; 
    // 配置纪元:故障转移时起作用,类似于哨兵的配置纪元 
    uint64_t configEpoch; 
    /*
     * 槽在该节点中的分布:占用16384/8个字节,16384个比特;
     * 每个比特对应一个槽:比特值为1,则该比特对应的槽在节点中;
     * 比特值为0,则该比特对应的槽不在节点中
     */
    unsigned char slots[16384/8]; 
    //节点中槽的数量 
    int numslots;
    …
}clusterNode;

【注】:除了上述字段,clusterNode结构还包含节点连接、主从复制、故障发现和转移需要的信息等。

② clusterState结构

clusterState结构保存了在当前节点视角下,集群所处的状态。主要字段包括:

typedef struct clusterState { 
    // 自身节点 
    clusterNode *myself; 
    // 配置纪元 
    uint64_t currentEpoch; 
    // 集群状态:在线还是下线 
    int state; 
    // 集群中至少包含一个槽的节点数量 
    int size; 
    //哈希表,节点名称 -> clusterNode节点指针 
    dict *nodes; 
    //槽分布信息:数组的每个元素都是一个指向clusterNode结构的指针;如果槽还没有分配给任何节点,则为NULL 
    clusterNode *slots[16384]; 
  … } clusterState;

【注】:除了上述字段,clusterState结构还包括故障转移、槽迁移等需要的信息。


往期精彩文章

Redis系列:

Redis系列(五):主从复制

Redis系列(四):布隆过滤器(Bloom Filter

Redis系列(三):Redis持久化机制(RDB & AOF)

Redis系列(二):跳跃表详解

Redis系列(一):与Redis的第一次相识

MySQL系列:

MySQL系列(五):精华篇

MySQL系列(四):MVCC多版本并发控制

MySQL系列(三):数据库锁

MySQL系列(二):索引

MySQL系列(一):事务隔离级别

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言