Administrator
发布于 2022-12-29 / 45 阅读
0
0

Redis集群

Redis的三种集群模式

集群的概念

在哨兵机制的时候说:多个实例作为一个小组,一起做同样的事(就是多个服务器做相同的事情),
就可以说是一个集群

集群可以解决单点故障问题,实现高可用

1、主从复制(读写分离)

可以解决单点问题,但不能实现故障的自动恢复

2、哨兵机制

高可用的实现方式之一:使用一个或者多个哨兵(Sentinel)实例组成的系统,对redis节点进行监控,
在主节点出现故障的情况下,能将从节点中的一个升级为主节点,进行故障转义,保证系统的可用性。

可以理解说:哨兵是在主从复制的基础上实现自动化

3、Redis-cluster集群

多主多从,多主实现了分布式数据存储,多主之间相互关联

是数据的分布式存储,数据的分布式存储

引申

集群是个物理形态,分布式是个工作方式。

集群:同一个业务,部署在多个服务器上,每个服务器都提供相同的服务,
那么这样系统的处理能力就相当于提升了好几倍,至于请求要哪个服务器处理,则涉及到负载均衡
区别集群的方式是根据部署多台服务器业务是否相同

分布式:一个业务分拆多个子业务,部署在不同的服务器上,多个服务器做不同的事情,
每个服务器只是负责整个项目的一部分功能
区别分布式的方式是根据不同机器不同业务。

从主从复制集群,想要提升性能,直接选择加服务器就行,但想要搞分布式,那么一开始的架构可能要入手
微服务了。

分布式中的每一个节点,都可以做集群。而集群并不一定就是分布式的。

分布式也属于微服务架构
只是微服务的应用不一定是分散在多个服务器上,也可以是同一个服务器。

为什么有Redis集群

Redis多主多从组成一个集群,但区别在于:哨兵机制中,redis每个实例是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,在支持高并发同时容纳海量的数据,采用分布式存储。即每台redis存储不同的内容。

Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库数据按一定规则分布存储在这N个节点中,每个节点存储总数据的1/N

有水平扩容就有纵向扩容,纵向扩容就是加设备(还大内存和磁盘),但和水平扩容相比会有个问题:当数据太大的时候,持久化会很耗时,主线程 fork 子进程时就可能会阻塞,后续想再加设备就会有点难,毕竟数据能无限大,内存不行

Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求

与哨兵机制相比
哨兵机制主从切换的过程中会丢失数据,因为只有一个 master。
只能单点写,没有解决水平扩容的问题。

Redis集群将数据按一定规则分布到不同的Redis节点,解决单点写,提高并发写,增加水平扩容

Redis分布式方案

之前,Redis分布式方案一般有两种:

客户端分区方案,例如用取模或者一致性哈希对 key 进行分片,查询和修改都先判断 key 的路由。
优点是分区逻辑可控,缺点是需要自己处理数据路由、高可用、故障转移等问题。

代理方案,代理服务做请求的转发。
优点是简化客户端分布式逻辑和升级维护便利,缺点是加重架构部署复杂度和性能损耗。

在Redis3.0之后提供了专有的集群方案:Redis Cluster。

Redis Cluster

数据分布

分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。

常见的分区规则有哈希分区和顺序分区(HBase)两种。Redis Cluster采用哈希分区规则。

哈希分区规则

1、节点取余分区

使用特定的数据,如Redis的键或用户ID,再根据节点数量N使用公式:hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。

优点是优点是简单,常用于数据库的分库分表规则。

缺点是当节点数量变化时,如扩容或收缩节点,数据节点映射关系需要重新计算,会导致数据的重新迁移。

2、一致性哈希分区

一致性哈希分区(Distributed Hash Table)实现思路是为系统中每个节点分配一个token,范围一般在0~2^32 ,这些token构成一个哈希环。

数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点

一致性

可以理解为一个圆,节点就是其中的一个点,要保存的键经过计算,也会得到一个点,
顺时针找比这个点大的节点所在的点就是要存入的节点了

优点:加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响
缺点:加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此这种方式不适合少量数据节点的分布式方案

3、虚拟槽分区

虚拟槽分区使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。

一般来说,自动创建集群,如果集群中有 N 个实例,那么,每个实例上的槽个数为 16384/N 个。
手动分配的话也可以按照每个实例的性能等进行分配不同的数量。

在手动分配哈希槽时,需要把 16384 个槽都分配完,否则Redis 集群无法正常工作。

Redis Cluser就采用虚拟槽分区。槽范围是0~16383。槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽。

具体的映射为:使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽

其实就是多少个槽归什么实例管而已,槽的作用可以理解为在管理数据和迁移时起作用,槽就相当于一个组嘛
实例就是一个班,再分班的时候那些一个组的就可以一起分了

Redis虚拟槽分区的特点:
1、解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
扩容和收缩只要增加或减少节点就行,加节点就将原来节点里管理的槽分一点出去,收缩就合进来

2、节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据。

3、支持节点、槽、键之间的映射查询(知道节点就知道槽,知道槽就知道键啦),
用于数据路由、在线伸缩等场景(不懂)。

集群功能限制

Redis集群相对单机在功能上存在一些限制

1、key批量操作支持有限。如mset、mget,目前只支持具有相同slot值的key执行批量操作。
2、key事务操作支持有限。只支持多key在同一节点上的事务操作(分布式事务相关)
3、key作为数据分区的最小粒度,因此不能将一个大的键值对象如hash、list等映射到不同的节点。
4、集群模式下只能使用一个数据库空间,即db0。单机16个。
5、复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。

集群搭建

等以后需要了再来,现在不想学。

节点通信

在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障等状态信息。

Redis集群采用P2P的Gossip(流言)协议,Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播。

Gossip消息

常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息等,所有的消息格式划分为:消息头和消息体。消息头包含发送节点自身状态数据,接收节点根据消息头就可以获取到发送节点的相关数据

meet

用于通知新节点加入

ping

每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。ping消息发送封装了自身节点和部分其他节点的状态数据。

pong

当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新

fail

当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态

通信节点选择

Redis集群内节点通信采用固定频率(定时任务每秒执行10次)。

因此每次选择要通信的节点十分重要,选择过多虽然可以做到信息及时交换但成本过高。节点选择过少会降低集群内所有节点彼此信息交换频率,从而影响故障判定、新节点发现等需求的速度。

而消息交换的成本主要体现在单位时间选择发送消息的节点数量和每个消息携带的数据量。

选择发送消息的节点数量

集群内每个节点维护定时任务默认每秒执行10次,每秒会随机选取5个节点找出最久没有通信的节点发送ping消息,用于保证Gossip信息交换的随机性。每100毫秒都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster_node_timeout/2,则立刻发送ping消息,防止该节点信息太长时间未更新

消息数据量

消息体携带数据量跟集群的节点数息息相关,更大的集群每次消息通信的成本也就更高,因此对于Redis集群来说并不是大而全的集群更好

集群伸缩

集群的伸缩就是节点的增减:节点增加就是扩容,减就是收缩。但是槽的数量是不变的,而槽是Redis集群管理数据的基本单位。

	所以,伸缩的原理就是:对原有节点的槽进行重分配。
    具体的原理不展开说了
    就比如公司固定100人,有5个部门的话,那每个部门就均分20人,某天加一个部门了,那就从原来5个部门
    里抽人过去第6个部门。某一天解散某个部门了,那就将解散的那个部门的人分到其他的部门里去。

请求路由

Redis集群对客户端通信协议做了比较大的修改,为了追求性能最大化,并没有采用代理的方式而是采用客户端直连节点的方式。

请求重定向

在集群模式下,客户端接收任何键相关命令时首先计算键对应的槽,再根据槽找出所对应的节点,如果节点有对应映射的哈希槽,则处理键命令;否则回复MOVED重定向错误,通知客户端请求正确的节点。这个过程称为MOVED重定向。

重定向信息包含了键所对应的槽以及负责该槽的节点地址,根据这些信息客户端就可以向正确的节点发起请求。

使用redis-cli命令时,可以加入-c参数支持自动重定向,简化手动发起重定向操作
redis-cli自动帮我们连接到正确的节点执行命令,这个过程是在redis-cli内部维护,
**实质上是client端接到MOVED信息之后再次发起请求,并不在Redis节点中完成请求转发**

为什么要重定向,是因为实例之间还可以通过相互传递消息,获得最新的哈希槽分配信息,
但是,客户端是无法主动感知这些变化的。这就会导致,它缓存的分配信息和最新的分配信息就不一致了。

为什么会不一致?
是因为实例和哈希槽的对应关系并不是一成不变的,最常见的变化有两个:

1、在集群中,实例有新增或删除,Redis 需要重新分配哈希槽;
2、为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍。

重定向-1672317066278

客户端为什么可以在访问任何一个实例时,都能获得所有的哈希槽信息呢?

Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信
息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。
**客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计
算键所对应的哈希槽,然后就可以给相应的实例发送请求了。**

如何让不同的键能映射到同一个槽

槽的计算规则:键内容包含 { 和 } 大括号字符,则计算槽的有效部分是括号内的内容;否则采用键的全内容计算槽。

其中键内部使用大括号包含的内容又叫做hash_tag,所以想在同一个槽,那么在键命名的时候加个hash_tag就行

ASK重定向

上面说到:实例和哈希槽的关系不是一成不变的,重新分配的时候会涉及到数据的迁移。那么在实际应用中,如果命令正好是正在进行数据迁移的节点该怎么办?

在这种迁移部分完成的情况下,客户端就会收到一条 ASK 报错信息。ASK 命令表示两层含义:第一,表明 Slot 数据还在迁移中;第二,ASK 命令把客户端所请求数据的最新实例地址返回给客户端。

ask-1672364250793

具体流程:
1、根据缓存中的哈希槽信息找对应的节点
2、节点正在迁移,所以返回ASK重定向
3、客户端从ASK重定向提取出目标节点信息,发送asking命令到目标节点打开客户端连接标识
4、发送命令
5、返回执行结果

和 MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽分配信息

所以迁移完成后再次发送一样的命令,会执行一次MOVED。ASK 命令的作用只是让客户端能给
新实例发送一次请求,而不像 MOVED 命令那样,会更改本地缓存,让后续所有命令都发往新实例。

Smart客户端

大多数开发语言的Redis客户端都采用Smart客户端支持集群协议

略,好长,暂时没想法

故障转移

Redis集群要实现高可用,不然和主从复制就没什么差别了。高可用首先需要解决集群部分失败的场景:当集群内少量节点出现故障时通过自动故障转移保证集群可以正常对外提供服务。

故障发现

Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。主要环节和哨兵机制的一样:分为主观下线(pfail)和客观下线(fail)。只不过对节点进行判定的不是哨兵,而是集群中的节点。

主观下线

某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。

集群中每个节点都会定期向其他节点发送ping消息,接收节点回复
pong消息作为响应。如果在cluster-node-timeout时间内通信一直失败,
则发送节点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。
客观下线

指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。

当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消
息在集群内传播。ping/pong消息的消息体会携带集群1/10的其他节点状
态数据,当接受节点发现消息体中含有主观下线的节点状态时,会保存到下线报告链表中。

通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告当半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流程。

为什么要半数以上的主节点
只有处理槽的主节点才负责读写请求和集群槽等关键信息维护
必须半数以上是为了应对网络分区等原因造成的集群分割情况

每个下线报告都存在有效期,当某节点在收集到半数以上的主节点的主观下线报告后,还要看看收集到的报告有没有过期,过期的下线报告将被删除。如果在cluster-node-time * 2 的时间内该下线报告没有得到更新则过期并删除

也就是说主观下线上报的速度追
赶不上下线报告过期的速度,那么故障节点将永远无法被标记为客观下
线从而导致故障转移失败。因此不建议将cluster-node-time设置得过小

完成客观下线之后,要进行广播fail消息,fail消息的消息体只包含故障节点的ID:

通知集群内所有的节点标记故障节点为客观下线状态并立刻生效。
通知故障节点的从节点触发故障转移流程。

故障恢复

当从节点通过内部定时任务发现自身复制的主节点进入客观下线时,将会触发故障恢复流程。

故障恢复流程

1、资质检查

判断和主节点最后的断线时间等进行筛选

2、准备选举时间

根据从节点的复制偏移量进行设置延迟选举时间,偏移量值越大,延迟越低。
相当于从侧面以偏移量优先
所有的从节点中复制偏移量最大的将提前触发故障选举流程

3、发起选举

当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程:

	1、更新配置纪元(相当于版本号)
	2、广播选举消息
    在集群内广播选举消息(FAILOVER_AUTH_REQUEST),从节点在一个配置纪元内只能发起一次选举

4、选举投票

只有持有槽的主节点才会处理故障选举消息(FAILOVER_AUTH_REQUEST),一个节点一张票
谁先来就投给谁。
如果都没有得到达到要求的票数,那么此次投票作废,等下一次投票

Redis集群没有直接使用从节点进行领导者选举,主要因为从节点
数必须大于等于3个才能保证凑够N/2+1个节点,将导致从节点资源浪
费。使用集群内所有持有槽的主节点进行领导者选举,即使只有一个从
节点也可以完成选举过程。

5、替换主节点

将原主节点的槽分到自己旗下管理
向集群广播自己的pong消息,通知集群内所有的节点当前从节点变为主节点并接管了故障主节点的槽信息

故障转移时间

主观下线(pfail)识别时间+主观下线状态消息传播时间+从节点转移时间


评论