【Redis】哨兵机制和集群


🔥个人主页: 中草药

🔥专栏:【中间件】企业级中间件剖析


一、哨兵机制

Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工的进行主从切换,同时需要大量的客户端需要被通知切换到了新的主节点之上,不适用于大规模的应用,因此这里用到了哨兵机制

基本概念

Redis的哨兵(Sentinel)机制是一种高可用性解决方案,旨在自动管理 主从复制 架构中的故障恢复。

名词 逻辑结构 物理结构
主节点 Redis 主服务 一个独立的 redis-server 进程
从节点 Redis 从服务 一个独立的 redis-server 进程
Redis 数据节点 主从节点 主节点和从节点的进程
哨兵节点 监控 Redis 数据节点的节点 一个独立的 redis-sentinel 进程
哨兵节点集合 若干哨兵节点的抽象组合 若干 redis-sentinel 进程
Redis 哨兵(Sentinel) Redis 提供的高可用方案 哨兵节点集合 和 Redis 主从节点
应用方 泛指一个多多个客户端 一个或多个连接 Redis 的进程

哨兵模式的配置文件

sentinel.conf

bash 复制代码
sentinel monitor 主节点名 主节点ip 主节点端口 法定票数
bash 复制代码
bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2
sentinel down-after-milliseconds redis-master 1000

法定票数涉及到后续的主观下线到客观下线

核心流程

​

监控

哨兵节点会以一定的频率向 Redis 集群中的所有主节点和从节点发送 PING 命令(心跳包),通过节点的响应来判断节点是否正常运行。每个哨兵节点都会维护一个关于主节点和从节点的状态信息列表,记录节点的存活状态、角色、连接信息等。

例如,当一个哨兵节点向主节点发送 PING 命令后,如果在规定的时间内(通常称为 down-after-milliseconds)没有收到响应,哨兵节点就会将该主节点标记为 "主观下线"(Subjectively Down,简称 SDOWN),即认为该节点可能出现了故障。

主观下线和客观下线

"主观下线" 只是单个哨兵节点的判断,为了避免误判,哨兵机制引入了 "客观下线"(Objectively Down,简称 ODOWN)的概念。当一个哨兵节点将主节点标记为 SDOWN 后,它会向其他哨兵节点发送 SENTINEL is-master-down-by-addr 命令,告知其他哨兵自己对该主节点的判断。当有足够数量(超过配置的 quorum 值)的哨兵节点都认为该主节点处于 SDOWN 状态时,这个主节点就会被标记为 ODOWN,即被认为是真正的故障。​

例如,假设集群中有 5 个哨兵节点,quorum 设置为 3。当其中一个哨兵节点发现主节点无响应并标记其为 SDOWN 后,它会向其他 4 个哨兵节点发送通知。如果有另外 2 个哨兵节点也认为该主节点 SDOWN,此时满足 quorum 条件,主节点就会被判定为 ODOWN,进入故障转移流程。

​

故障转移和通知

一旦主节点被判定为 ODOWN,哨兵机制就会触发故障转移流程。具体步骤如下:​

*1)选举领头哨兵:在所有哨兵节点中,通过 Raft 算法选举出一个领头哨兵(Leader Sentinel),由它来负责执行故障转移操作。选举过程中,每个哨兵节点都会向其他节点发送投票请求,获得超过半数投票的哨兵节点将成为领头哨兵。​

*2)选择新主节点:领头哨兵从当前存活的从节点列表中,按照一定的规则选择一个从节点作为新的主节点。选择规则通常基于从节点的优先级(slave-priority 配置项,数值越小优先级越高)、复制偏移量(优先选择复制偏移量最大,即数据最完整的从节点)以及运行 ID(随机选择)。​

3)提升新主节点:领头哨兵向选中的从节点发送 SLAVEOF no one 命令,将其提升为新的主节点。此时,新主节点开始接受客户端的写入请求。​

4)重新配置其他从节点:领头哨兵向其他从节点发送 SLAVEOF 命令,让它们指向新的主节点,开始从新主节点进行数据复制。​

5)通知客户端:领头哨兵通过发布与订阅机制,向所有连接到 Redis 集群的客户端发送新主节点的地址信息,以便客户端能够重新连接到新的主节点。


注意

1)哨兵节点数量不能为单一,避免其故障影响系统可用性。

2)哨兵节点宜为奇数个,利于选举 leader 。

3)哨兵节点不承担数据存储任务,由 Redis 主从节点负责存储。

4)哨兵 + 主从复制可提高系统可用性,但无法解决极端情况下数据写丢失问题 。

5)哨兵 + 主从复制不能提升数据存储容量,面对接近或超 机器物理内存的数据量时存在局限。

二、集群Cluster

这里的集群指的是实现的是拓展内存空间

Redis Cluster 是 Redis 官方提供的分布式解决方案,旨在解决单机 Redis 的性能瓶颈、数据容量限制和高可用性问题,他通过引入多组的Master/Slave,每一组的Master/Slave存储数据全量的一部分,从而构成一个更大的整体,称为Redis集群(Cluster).

​

每一个红框部分被称为是一个分片(Sharding)

数据分片算法

1、哈希求余算法

也叫除法取余哈希 ,是一种简单的哈希算法。将输入数据Key(比如使用Md5算法)通过除法运算后取余数,得到特定范围内的哈希值。比如对于整数,用整数除以N取余,余数就是哈希值。

MD5算法是广泛使用的哈希算法,他的主要特点有:

1、计算的结果是定长的

可处理任意长度数据,输出始终是 128 位二进制数(转换为十六进制是 32 个字符 )。

2、计算结果是发散的

两个字符串即使只有细小差别,计算出的结果差别也是很大

2、计算结果不可逆

在一些哈希表实现中,先计算键值的 hashCode 值,再对数组长度取余(有时用位运算实现 ),得到的余数作为存储下标 。例如有数据 [10, 20, 30] ,设定除数为 7 ,则 10 % 7 = 3 、20 % 7 = 6 、30 % 7 = 2 ,3、6、2 就是对应哈希值。

优点:简单高效,数据分配均匀

缺点 :一旦需要扩容,N改变了,原有的映射规则改变,需要让节点之间的数据相互传输,重新排列,满足新的映射规则,此时需要搬运的数据量是比较大的,开销较大

​

如上图一共有21个key,仅仅只有3个key是没有经过搬运的


2、一致性哈希算法

构建一个逻辑上的环形结构,是 0 到 2^32-1(或其他取值范围 )的哈希值空间,数据按照顺时针方向增长。每个节点(物理节点或虚拟节点 )通过哈希算法映射到环上某个位置 。

​

数据经哈希算法映射到环上位置,存储在顺时针方向第一个遇到的节点上 。比如有服务器节点 A、B、C 映射在环上,数据 D 经哈希后落在环上某点,按顺时针找,第一个遇到节点 B,D 就存储在 B 。

优点:大大降低了扩容时数据搬运的规模,提高了扩容操作的效率

缺点 :会出现数据分配不均匀,即数据倾斜

3、哈希槽分区算法(Redis使用)

集群通过哈希槽(Hash Slot)实现数据分片,共 16384 个哈希槽。键经 CRC16 算法计算哈希值并对 16384 取模,确定所属哈希槽,哈希槽分配到不同主节点 ,每个主节点管理部分哈希槽及对应数据。

复制代码
hash_slot = crc16(key) % 16384

其中 crc16 也是一种hash算法

16384实际上就是16*1024 也就是 2^14

该算法的本质相当于是哈希求余和一致性哈希的一种结合,解决了数据倾斜和数据搬运成本高的问题

这里的分片规则是很灵活的.每个分片持有的槽位也不一定连续.。

每个分片的节点使用 位图 来表示自己持有哪些槽位.对于16384个槽位来说,需要2048个字节(2KB)大小的内存空间表示.

常见问题

1、Redis集群最多有16384个分片吗

如果一个分片一个槽位的话反而会出现数据分布不均匀的情况,实际上Redis作者建议集群分布数不应该超过1000

2、为什么是16384个槽位

官方回答:github.com

翻译过来大概意思是:

节点之间通过心跳包通信。心跳包中包含了该节点持有哪些 slots. 这个是使用位图这样的数据结构表示的。表示 16384 (16k) 个 slots, 需要的位图大小是 2KB. 如果给定的 slots 数更多了,比如 65536 个了,此时就需要消耗更多的空间,8 KB 位图表示了.。8 KB, 对于内存来说不算什么,但是在频繁的网络心跳包中,还是一个不小的开销.

另一方面,Redis 集群一般不建议超过 1000 个分片。所以 16k 对于最大 1000 个分片来说是足够用的,同时也会使对应的槽位配置位图体积不至于很大.

故障处理

主节点宕机

我们可以在docker中模拟建造一个集群关系

​

在上述的集群关系之中挑一个随便停掉

复制代码
docker stop redis1

连上redis2

​

可以看到ip地址101的节点已经提示fail,然后原本是从节点的105成了新的主节点

此时重新启动redis1

bash 复制代码
docker start redis1

再进行观察

​

此时的101重新加入到集群之中但是是作为从节点

可以登陆到101客户端执行cluster failover进行集群恢复,也就是把101重新设为master


处理流程

故障判定

集群中的所有节点,都会周期性的使用心跳包进行通信.

1)节点 A 给节点 B 发送 ping 包,B 就会给 A 返回一个 pong 包. ping 和 pong 除了 message type 属性之外,其他部分都是一样的。这里包含了集群的配置信息 (该节点的 id, 该节点从属于哪个分片,是主节点还是从节点,从属于谁,持有哪些 slots 的位图...).

2)每个节点,每秒钟,都会给一些随机的节点发起 ping 包,而不是全发一遍。这样设定是为了避免在节点很多的时候,心跳包也非常多 (比如有 9 个节点,如果全发,就是 9 * 8 有 72 组心跳了,而且这是按照 N^2 这样的级别增长的).

3)当节点 A 给节点 B 发起 ping 包,B 不能如期回应的时候,此时 A 就会尝试重置和 B 的 tcp 连接,看能否连接成功。如果仍然连接失败,A 就会把 B 设为 PFAIL 状态 (相当于主观下线).

4)A 判定 B 为 PFAIL 之后,会通过 redis 内置的 Gossip 协议,和其他节点进行沟通,向其他节点确认 B 的状态. (每个节点都会维护一个自己的 "下线列表", 由于视角不同,每个节点的下线列表也不一定相同).

5)此时 A 发现其他很多节点,也认为 B 为 PFAIL, 并且数目超过总集群个数的一半 ,那么 A 就会把 B 标记成 FAIL (相当于客观下线), 并且把这个消息同步给其他节点 (其他节点收到之后,也会把 B 标记成 FAIL).

至此,B 就彻底被判定为故障节点了.

某个或者某些节点宕机,有的时候会引起整个集群都宕机 (称为 fail 状态).

以下三种情况会出现集群宕机:

  • 某个分片,所有的主节点和从节点都挂了,该分片无法提供服务
  • 某个分片,主节点挂了,但是没有从节点.
  • 超过半数的 master 节点都挂了.

核心原则是保证每个slots都能够正常工作

故障迁移

上述例子中,B 故障,并且 A 把 B FAIL 的消息告知集群中的其他节点.

  • 如果 B 是从节点,那么不需要进行故障迁移.
  • 如果 B 是主节点,那么就会由 B 的从节点 (比如 C 和 D) 触发故障迁移了.

所谓故障迁移,就是指把从节点提拔成主节点 ,继续给整个 redis 集群提供支持.

具体流程如下:

1)从节点判定自己是否具有参选资格。如果从节点和主节点已经太久没通信 (此时认为从节点的数据和主节点差异太大了), 时间超过阈值,就失去竞选资格.

2)具有资格的节点,比如 C 和 D, 就会先休眠一定时间。休眠时间 = 500ms 基础时间 + 0,500ms 随机时间 + 排名 * 1000ms.offset 的值越大,则排名越靠前 (越小).

3)比如 C 的休眠时间到了,C 就会给其他所有集群中的节点,进行拉票操作。但是只有主节点才有投票资格.

4)主节点就会把自己的票投给 C (每个主节点只有 1 票). 当 C 收到的票数超过主节点数目的一半,C 就会晋升成主节点.(C 自己负责执行 slaveof no one, 并且让 D 执行 slaveof C).

5)同时,C 还会把自己成为主节点的消息,同步给其他集群的节点。大家也都会更新自己保存的集群结构


集群扩容

集群扩容本身是一件风险较高,成本比较大的操作

1)把新的主节点加入到集群

bash 复制代码
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379

前一个ip是被新增的节点,后一个ip表示集群上的任意一个节点

​

此时可以注意到该节点已被加入到集群但是还未分配slots

2)重新分配slots

bash 复制代码
redis-cli --cluster reshard 172.30.0.101:6379

reshard 后的地址是集群中的任意节点地址.

另外,注意单词拼写,是 reshard (重新切分), 不是 reshared (重新分享), 不要多写个 e

此时会询问多少个slots要进行shard

​

那些节点来接受这些slots,此处应该是我们新加入的节点

​

这些节点从哪里搬过来

​

all, 表示从其他每个持有 slots 的 master 都拿过来点.

手动指定,从某一个或者某几个节点来移动 slots (以 done 为结尾)

之后会先给出搬运的计划,并未实施真正的搬运操作,当输入yes之后,才会开始真正的搬运过程,不仅仅是slots的重新分配,也是数据的搬移,因此是一个比较重量的操作

​

3)为该主节点添加新的从节点

redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id 172.30.1.110 节点的 nodeId

此时集群扩容才算是真正完成

在搬运slots/数据的时候,客户端能否正常访问集群?

大部分未搬运的 key 对应的业务可以正常访问。例如客户端访问某 key,经集群分片算法判定该 key 所在分片未进行相关数据搬运,就能正常重定向到对应节点并操作 。

而针对需要进行搬运的key会存在访问错误的情况,若客户端访问的 key 正好处于搬运过程中,比如已从原节点搬走但还未完全在新节点就绪,此时访问会失败 。例如客户端请求某 key,集群按算法重定向到对应节点,却发现该 key 已被迁移走,就无法正常响应 。

因此,针对生产环境的扩容,可以在流量小的情况下进行,把损失降到最低。

很明显,要想追求更高的可用性,让扩容对于用户影响更小,就需要搞一组新的机器,重新搭建集群,并且把数据导入过来,使用新集群代替旧日集群.(成本最高的)。


自信与骄傲有异:信者常沉着,而骄傲者常浮扬。 ------梁启超

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀

以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐

制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸

相关推荐
骉马代驾13 分钟前
代驾系统实战(三):抢单池的 Redis 锁 + RabbitMQ 异步消费怎么做
redis
邓工说电1 小时前
智慧断路器安全吗?数据加密、离线保护与合规认证全解读
大数据·数据库·人工智能·智能断路器·炜晔科技
adinnet20261 小时前
企业微调大模型(Fine-tuning):先吃透 RAG 还是先训自己的模型
大数据·数据库
行百里er2 小时前
Redis Streams——有确认、能回溯的消息队列
redis
上位机妹子2 小时前
EF Core 两种继承映射:每层次一张表(TPH)与每类型一张表(TPT)
数据库·c#·.netcore·ef core
小马同学-2 小时前
Redis消息队列与客户端编程
数据库·redis
weixin199701080162 小时前
《1688图片空间API踩坑:img.upload 与 album.* 的防盗链与CDN缓存问题》(附Python源码)
开发语言·python·缓存
for_ever_love__2 小时前
MySQL 高频面试题 30 问:索引、事务、锁、日志、主从与分库分表(MySQL 8.0 版)
java·数据库·spring boot·mysql·spring cloud·面试·总结
染指11103 小时前
140.Agent-多Agent框架-Agent执行Skills流程
数据库·人工智能·设计模式·langchain·agents