一、什么是分片技术(Cluster)?

​ 我们知道主从复制哨兵机制保障了高可用,就读写分离而言虽然slave节点扩展了主从的读并发能力,但是写能力存储能力是无法进行扩展,就只能是master节点能够承载的上限。如果面对海量数据,那么必然需要构建master(主节点分片)之间的集群,同时必然需要吸收高可用(主从复制和哨兵机制)能力,即每个master分片节点还需要有slave节点,这是分布式系统中典型的横向扩展(集群的分片技术)的体现,所以在Redis 3.0版本中对应的设计就是Redis Cluster

​ 其实出了构建master集群,我们也可以升级单个 Redis 的硬件配置,比如增加内存容量、磁盘容量,这属于纵向扩展。纵向扩展受限于硬件和成本,而横向扩展需要解决分布式管理的问题。

二、节点(clusterNode)

​ 一个Redis集群通常由多个节点(node)组成,在刚开始的时候, 每个节点都是相互独立的,它们都处于一个只包含自己的集群当中,要组建一个真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。

一个节点就是一个运行在集群模式下的Redis服务器,Redis服务器 在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5e8QPSPc-1660643410961)(E:/Blog/lansg/source/img/image-20220509182227366.png)]

​ clusterNode结构保存了一个节点的当前状态,比如节点的创建时间节点的名字节点当前的配置纪元节点的IP地址和端口号等等。 每个节点都会使用一个clusterNode结构来记录自己的状态,并为集群中的所有其他节点(包括主节点和从节点)都创建一个相应的 clusterNode结构,以此来记录其他节点的状态。

struct clusterNode {
    
	//创建节点的时间
	mstime_t ctime;
    
	//节点的名字,由40个十六进制字符组成
	例如68eef66df23420a5862208ef5b1a7005b806f2ff
	char name[REDIS_CLUSTER_NAMELEN];

	//节点标识
	//使用各种不同的标识值记录节点的角色(比如主节点或者从节点)
    //以及节点目前所处的状态(比如在线或者下线)。
	int flags;
    
	//节点当前的配置纪元,用于实现故障转移
	uint64_t configEpoch;
    
	//节点的IP地址
	char ip[REDIS_IP_STR_LEN];
    
	//节点的端口号
	int port;

	//保存连接节点所需的有关信息
	clusterLink *link;
	...
};

​ 每个节点都保存着一个clusterState结构,这个结构记录了在当前节点的视角下,集群目前所处的状态,例如集群是在线还是下线, 集群包含多少个节点等等。

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

三、哈希槽指派

​ Redis-cluster没有使用一致性hash,而是引入了哈希槽的概念,每个key通过CRC16(key)&16383来决定放置哪个槽。Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384(2的14次方)个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点负责一部分槽位,可以处理0个或最多16384个槽。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qbj2Dqng-1660643410963)(E:/Blog/lansg/source/img/image-20220816172206929.png)]

​ 当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群 处于下线状态(fail)

eg.执行以下命令可以将槽0至槽5000指派给端口为7000的节点负责

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HD2BF6kv-1660643410964)(E:/Blog/lansg/source/img/image-20220509183511584.png)]

clusterNode结构的slots属性numslot属性记录了节点负责处理哪些槽:

struct clusterNode {
	// ...
	unsigned char slots[16384/8];
	int numslots;
	// ...
};

slots属性是一个二进制位数组(bit array),这个数组的长度为 16384/8=2048个字节,共包含16384个二进制位。 Redis以0为起始索引,16383为终止索引,对slots数组中的16384个 二进制位进行编号,并根据索引i上的二进制位的值来判断节点是否负责处理槽i:

  • 如果slots数组在索引i上的二进制位的值为1,那么表示节点负责处理槽i。
  • 如果slots数组在索引i上的二进制位的值为0,那么表示节点不负责处理槽i。

一个节点会将自己的slots数组通过消息发送给集群中的其他节点,以此告知其他节点自己目前负责处理哪些槽。

clusterState结构中的slots数组记录了集群中所有16384个槽的指派信息:

typedef struct clusterState {
	// ...
	clusterNode *slots[16384];
	// ...
} clusterState;

slots数组包含16384个项,每个数组项都是一个指向clusterNode结构的指针:

  • 如果slots[i]指针指向一个clusterNode结构,那么表示槽i已经指派 给了clusterNode结构所代表的节点。

简单说一下为什么采用16384个槽?

CRC16 算法,产生的hash值有 16 bit 位,可以产生 65536(2^16)个值 ,也就是说值分布在 0 ~ 65535 之间,但为什么槽数确实16384呢?

正常的心跳数据包携带节点的完整配置,它能以幂等方式来更新配置。如果采用 16384 个插槽,占空间 2KB (16384/8);如果采用 65536 个插槽,占空间 8KB (65536/8)。Redis Cluster 不太可能扩展到超过 1000 个主节点,太多可能导致网络拥堵。16384 个插槽范围比较合适,当集群扩展到1000个节点时,也能确保每个master节点有足够的插槽,8KB 的心跳包看似不大,但是这个是心跳包每秒都要将本节点的信息同步给集群其他节点。比起 16384 个插槽,头大小增加了4倍,ping消息的消息头太大了,浪费带宽。

四、Cluster总线

每个Redis Cluster节点有一个额外的TCP端口用来接受其他节点的连接。这个端口与用来接收client命令的普通TCP端口有一个固定的offset。该端口等于普通命令端口加上10000.例如,一个Redis服务器在端口6379接受客户端连接,那么它的集群总线端口16379也会被打开。

五、请求重定向

Redis cluster采用去中心化的架构,集群的主节点各自负责一部分槽,客户端如何确定key到底会映射到哪个节点上呢?这就要用到请求重定向。

在cluster模式下,节点对请求的处理过程如下:

1.检查当前key是否存在当前NODE?

  • 通过crc16(key)&16384计算出slot
  • 查询负责该slot负责的节点,得到节点指针
  • 该指针与自身节点比较

2.若slot不是由自身负责,则返回MOVED重定向;

3.若slot由自身负责,且key在slot中,则返回该key对应结果;

4.若key不存在此slot中,检查该slot是否正在迁出(MIGRATING)?

5.若key正在迁出,返回ASK错误重定向客户端到迁移的目的服务器上;

6.若Slot未迁出,检查Slot是否导入中?

7.若Slot导入中且有ASKING标记,则直接操作;

否则返回MOVED重定向。

解释一下什么是MOVED重定向和ASK重定向:

redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),所以 相关槽所属的键值对也会从源节点被动迁移到目标节点。

Moved 重定向

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ljKHZiJO-1660643410964)(E:/Blog/lansg/source/img/image-20220509203448546.png)]

  • 槽命中:直接返回结果
  • 槽不命中:即当前键命令所请求的键不在当前请求的节点中,则当前节点会向客户端发送一个Moved 重定向,客户端根据Moved 重定向所包含的内容找到目标节点,再一次发送命令。
ASK重定向

Ask重定向发生于集群伸缩时,集群伸缩会导致槽迁移,当我们去源节点访问时,此时数据已经可能已经迁移到了目标节点,使用Ask重定向来解决此种情况。

E:/Blog/lansg/source/img/image-20220509203554844-16606434714713.png

smart客户端

上述两种重定向的机制使得客户端的实现更加复杂,提供了smart客户端(JedisCluster)来减低复杂性,追求更好的性能。客户端内部负责计算/维护键-> 槽 -> 节点映射,用于快速定位目标节点。

实现原理:

  • 从集群中选取一个可运行节点,使用 cluster slots得到槽和节点的映射关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zyv14Bf3-1660643410965)(E:/Blog/lansg/source/img/image-20220509204132805.png)]

  • 将上述映射关系存到本地,通过映射关系就可以直接对目标节点进行操作(CRC16(key) -> slot -> node),很好地避免了Moved重定向,并为每个节点创建JedisPool。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZTQu6EX0-1660643410965)(E:/Blog/lansg/source/img/image-20220509204203467.png)]

六、状态维护

1.Gossip协议

Redis 集群是去中心化的,彼此之间状态同步靠 gossip 协议通信,集群的消息有以下几种类型:

  • Meet 通过「cluster meet ip port」命令,已有集群的节点会向新的节点发送邀请,加入现有集群。
  • Ping 节点每秒会向集群中其他节点发送 ping 消息,消息中带有自己已知的两个节点的地址、槽、状态信息、最后一次通信时间等。
  • Pong 节点收到 ping 消息后会回复 pong 消息,消息中同样带有自己已知的两个节点信息。
  • Fail 节点 ping 不通某节点后,会向集群所有节点广播该节点挂掉的消息。其他节点收到消息后标记已下线。

2.心跳的实现和维护

什么时候发送心跳?

​ Redis节点会记录其向每一个节点上一次发出ping和收到pong的时间,心跳发送时机与这两个值有关。通过下面的方式既能保证及时更新集群状态,又不至于使心跳数过多:

  • 每次Cron向所有未建立链接的节点发送ping或meet

  • 每1秒从所有已知节点中随机选取5个,向其中上次收到pong最久远的一个发送ping

  • 每次Cron向收到pong超过timeout/2的节点发送ping

  • 收到ping或meet,立即回复pong

发送哪些心跳数据?

  • Header,发送者自己的信息

    (1)所负责slots的信息

    (2)主从信息

    (3)ip port信息

    (4)状态信息

  • Gossip,发送者所了解的部分其他节点的信息

    (1)ping_sent, pong_received

    (2)ip,port信息

    (3)状态信息,比如发送者认为该节点已经不可达,会在状态信息中标记其为PFAIL或FAIL

Gossip的存在使得集群状态的改变可以更快的达到整个集群。每个心跳包中会包含多个Gossip包,那么多少个才是合适的呢,redis的选择是N/10,其中N是节点数,这样可以保证在PFAIL投票的过期时间内,节点可以收到80%机器关于失败节点的gossip,从而使其顺利进入FAIL状态。

七、扩容和缩容

扩容

1.首先将新节点加入到集群中,可以通过在集群中任何一个客户端执行cluster meet 新节点ip:端口,或者通过redis-trib add node添加,新添加的节点默认在集群中都是主节点。

2.迁移数据 迁移数据的大致流程是,首先需要确定哪些槽需要被迁移到目标节点,然后获取槽中key,将槽中的key全部迁移到目标节点,然后向集群所有主节点广播槽(数据)全部迁移到了目标节点。

缩容

缩容的大致过程与扩容一致,需要判断下线的节点是否是主节点,以及主节点上是否有槽,若主节点上有槽,需要将槽迁移到集群中其他主节点,槽迁移完成之后,需要向其他节点广播该节点准备下线。最后需要将该下线主节点的从节点指向其他主节点,当然最好是先将从节点下线。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐