前言
集群,即Redis Cluster,是Redis3.0开始引入的分布式存储方案。集群是由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点;只有主节点负责读写请求和集群信息的维护,从节点只进行主节点数据和状态信息的复制。
上图是Redis Cluster典型的架构图,集群中的每一个Redis节点都是互相连通的,客户端任意直连到集群中的任意一个节点,就可以对其他Redis节点进行读写操作。
集群的作用
集群的作用,主要可以归纳为以下两点:
- 数据分区:又称为数据分片,是集群最核心的功能!集群将数据分散到多个节点,一方面突破了Redis单机内存大小限制【单机内存大小受限制问题:如果单机内存太大,bgsave和bgrewriteaof的fork操作可能导致主线程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出,等等】,存储容量大大增加;另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。
- 高可用:集群支持主从复制和主节点的自动故障转移,当任一节点发生故障时,集群仍然可以对外提供服务。
集群的基本原理
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:根据槽和节点的映射关系,计算数据属于哪个节点。
- 节点通信机制
集群作为一个整体工作,是离不开节点之间的通信的。
- 两个端口
在哨兵机制中,节点分为数据节点和哨兵节点:数据节点存储数据,哨兵节点实现额外的控制功能。在集群中,是没有数据节点和哨兵节点之分的,所有的节点都是存储数据,也都参与集群状态的维护。为此,集群中的每个节点都提供另外两个TCP端口:
① 普通端口:主要用于为客户端提供服务(与单机节点类似),在节点间数据迁移时也会使用该端口;
② 集群端口:端口号是普通端口+10000(10000是固定值,无法改变)。集群端口只用于节点之间的通信,比如:搭建集群、增减节点、故障转移等操作时节点之间的通信
【注】:要特别注意两点:① 不要使用客户端连接集群端口;② 为了保证集群可以正常工作,在配置防火墙时,要同时开启普通端口和集群端口。
- Gossip协议
节点之间通信,按照通信协议可以分为几种类型:单对单、广播、Gossip协议等。
广播是指向集群内所有节点发送消息;优点:集群的收敛速度快(集群收敛是指集群内所有节点获得集群信息是一致的);缺点:每条消息都要发送给所有的节点,CUP、宽带等消耗都比较大。
Gossip协议:在节点数量有限的网络中,每个节点都“随机”的与部分节点通信(并不是真正的随机,而是根据特定的规则选择通信的节点);经过通信,每个节点的状态很快达到一致。优点:负载低、去中心化、容错性高(因为通信有冗余)等;缺点:集群的收敛速度慢。
- 消息类型
集群中的节点采用固定频率(每秒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命令。
- 数据结构
节点需要专门的数据结构来存储集群的状态。这里所谓集群的状态,是一个宽泛的概念,包括:集群是否处于上线状态、集群中有哪些节点、节点是否可达、节点的主从状态、槽的分布等等。
节点为了存储集群状态而提供的数据结构中,最关键的是clusterNode和clusterState结构:前者记录了一个节点的状态,后者记录了集群作为一个整体的状态。
① 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持久化机制(RDB & AOF)
MySQL系列: