Administrator
发布于 2022-12-26 / 67 阅读
0
0

Redis哨兵

Redis的高可用问题

在主从复制中说,主从复制可以解决单点登录问题,也能为主节点分担读请求的压力。

但在最后,都只是在说了主从复制,至于如何实现解决单点问题,只是说到了主节点出现故障时,从节点可以顶上来当做新的主节点,但这一过程是要人为去干预的,故障转移实时性和准确性上都无法得到保障。

在实际中,业务可能要实现高可用,那么要人为去干预是不合理的,为此要将主从切换的流程自动化,然而自动化的时候就会有几个问题:

1、判断节点不可达的机制是否健全和标准(怎么知道主节点是不是真的挂了)。
2、如果有多个从节点,怎样保证只有一个被晋升为主节点(该选择哪一个从库进行切主)。
3、通知客户端新的主节点机制是否足够健壮(新主节点的信息能不能及时有效的通知从库和客户端)。

为了解决这些问题,就要提到哨兵机制了。在 Redis 主从集群中,哨兵机制是实现主从库自动切换的关键机制,它有效地解决了主从复制模式下故障转移的这三个问题。

哨兵机制实现原理

基本流程

哨兵其实就是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、主观下线和客观下线、选主、通知。

监控

一套合理的监控机制是哨兵判定节点不可达的重要保证,哨兵通过三个定时监控任务完成对各个节点发现和监控:

监控一

每隔10秒,每个哨兵会向主节点和从节点发送info命令获取最新的拓扑结构。

eg:

以下是执行 info replication后得到的信息,
而执行info,得到的信息会更全面,会包含info replication得到的信息
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=230653,lag=0
master_replid:6484dc5f3a85fa12266df9bcb5c2b9f34fc51c2d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:230653
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:230653

哨兵通过周期性的发送info命令有以下好处:

解析得到信息,就可以找到相应的从节点,所以哨兵在配置时不用配置从节点,配置主节点即可;
且当有新的从节点加入时都可以立刻感知;
从节点不可达或者故障转移后,可以通过info命令实时更新节点拓扑信息。

监控二

这个监控的实现主要依赖于Redis的订阅发布功能,只有订阅了同一个频道的应用,才能通过发布的消息进行信息
交换:

每隔2秒,每个哨兵会向Redis数据节点(主节点)的__sentinel__:hello频道上发送该哨兵对于主节点的判断以及当前哨兵的信息,同时每个哨兵也会订阅该频道,来了解其他哨兵以及它们对主节点的判断。

类比如:

哨兵通信

哨兵的发布订阅能完成以下工作:

1、发现其他的哨兵,并与之通信
2、交换主节点的状态,作为后面客观下线以及主节点选举的依据

监控三

每隔1秒,每个哨兵会向主节点、从节点、其余哨兵发送一条ping命令做一次心跳检测,来确认这些节点当前是否可达。 实现了对每个节点的监控,这个定时任务是节点失败判定的重要依据。

上面两步监控就能和主节点,其余哨兵进行连接了的,而从节点则是因为在监控一里
能获取到所有的从节点信息,然后哨兵也能对从节点建立连接

监控

主观下线和客观下线

在第三个监控任务中,哨兵需要判断主库是否处于下线状态;下线状态又分为主观下线和客观下线

主观下线

主观与否是对哨兵而言的,在第三个监控任务中,如果哨兵向某个节点发送ping命令做心跳检测,当这些节点
超过down-after-milliseconds没有进行有效回复,哨兵就会对该节点做失败判定,这个行为叫做主观下线。

就好像回消息一样,你发一条消息给别人,别人在某时间段内没回复,你判定别人没看手机。
这是一种主观层面的臆断,不一定准确的

客观下线

在对节点进行检测的时候,如果是从节点被标记为下线了还好,因为从节点只用于读操作,影响不大。
但如果被标记的是主节点,那就必须进行客观下线判断。因为如果哨兵误判了,主库并没有故障。然后又启动了主从切换,后续的选主和通知操作都会带来额外的计算和通信开销

误判一般会发生在集群网络压力较大、网络拥塞,或者是主库本身压力较大的情况下。

为了减少误判这样的问题,哨兵机制通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。
上面说的多个哨兵之间建立了连接也可以认为是一个集群。

这种情况下,当某个哨兵判定主节点为主观下线后,该哨兵会通过sentinel is-master-down-by-addr命令向其他哨兵询问对主节点的判断,当超过< quorum >个数,哨兵会认为主节点确实有问题,这时该哨兵会做出客观下线的决定

“客观下线”的标准就是,当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为“主观下线”,(所以实例个数N要大于等于3,才能进行选举)
才能最终判定主库为“客观下线”。这样一来,就可以减少误判的概率,也能避免误判带来的无谓的主从库切换。
(当然,有多少个实例做出“主观下线”的判断才可以,可以由 Redis 管理员自行设定,这个数就是<quorum>)。
总的来说,就是少数服从多数,以减少部分因网络等原因造成的误判

从节点、哨兵在主观下线后,没有后续的故障转移操作。主节点在主观下线后要进行客观下线才会准备进行故障转移

集群这个概念其实是要和哨兵分开按顺序来的,但在两本书中看到的其实都是有交互的,不如及早加入,反正简单的理解:多个哨兵组合在一起,一起做事就是一个集群就好了

选主

哨兵选举

在已经确定主节点客观下线后,按步骤是要准备进行选主然后故障转移了的,但是选主由一个哨兵来完成就可以。那么在集群中有那么多个哨兵,究竟由哪个进行呢?这个时间就要在哨兵中进行一个选举,选出一个哨兵作为领导者进行故障转移的工作。Redis使用了Raft算法实现领导者(Leader)选举这个算法书里不细介绍,我现在也不想研究,那就用书中的例子说明好了。

选举的规则

任何一个想成为Leader的哨兵,要满足两个条件:第一,拿到半数以上的赞成票;第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值

以 3 个哨兵为例,假设此时的 quorum 设置为 2,那么,任何一个想成为 Leader 的哨兵只要拿到2 张赞成票

时间 哨兵1(S1) 哨兵2(S2) 哨兵3(S3)
T1 S1在完成客观下线后,向S2,S3发送命令进行投票申请,表示自己想成为Leader,并给自己投了一票
T2 S3在完成客观下线后,向S1,S3发送命令进行投票申请,表示自己想成为Leader,并给自己投了一票
T3 收到S3请求,因为已投了自己,所以不投S3 收到S3等请求,因为没投过,所以可以投给S3
T4 收到S1的请求,因为已经投过了,所以不投S1
T5 只有自己的那一票 有自己的一票,S2的一票
在T5时刻,S3已经拿到了两张票,且quorum等于2,那么最后就是S3作为Leader进行选主操作。
而S1因为只有一票,没有达到半数以上,且小于quorum,所以不会成为Leader。


如果最后没有任何一个哨兵能得到两张票成为Leader,那么哨兵集群会等待一段时间
(也就是哨兵故障转移超时时间的 2 倍),再重新选举。这是因为,哨兵集群能够进行成
功投票,很大程度上依赖于选举命令的正常网络传播。如果网络压力较大或有短时堵塞,
就可能导致没有一个哨兵能拿到半数以上的赞成票。所以,等到网络拥塞好转之后,再进
行投票选举,成功的概率就会增加。
选举的疑问
1、要是在T1时刻,所有哨兵都完成客观下线,并把票投给了自己,那岂不是永远选不出Leader?

给出的解释是:不同哨兵的网络连接、系统压力不完全一样,接收到下线协商消息的时间
也可能不同,所以,它们同时做出主库客观下线判定的概率较小,哨兵对主从库进行的在线状态检查等操作,
是属于一种时间事件,用一个定时器来完成,一般来说每100ms执行一次这些事件。每个哨兵的定时器执行周期
都会加上一个小小的随机时间偏移,目的是让每个哨兵执行上述操作的时间能稍微错开些,也是为了避免它们都
同时判定主库下线,同时选举Leader。即使出现了都投给自己一票的情况,导致无法选出Leader,
哨兵会停一段时间(一般是故障转移超时时间failover_timeout的2倍),然后再可以进行下一轮投票。

2、哨兵的投票规则?
自己没收到别人的请求且自己也要进行选举则投给自己,收到别人请求,谁先来就给谁。

3、客观下线的疑问
一个哨兵发现主节点下线后,会发送命令询问其他的哨兵,假如这个主节点是真的下线了,
那么其他哨兵也会得到这个节点已下线的结论,这不就会造成都开始进行Leader的选举了嘛?

这个问题我个人觉得是:除了网络堵塞等的问题之外,基本都是谁先来就是谁了,先确定客观下线,
就会继续进行选举操作。

选主(故障转移)

上面选举出来的哨兵(Leader)会负责故障转移。步骤如下:

  1. 在从节点列表中选出一个节点作为新的主节点,选择方法如下:

    1、过滤:“不健康”(主观下线、断线)、5秒内没有回复过哨兵ping响应、
    与主节点失联超过down-after-milliseconds*10秒。
    2、选择slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在则继续。
    3、选择复制偏移量最大的从节点(复制的最完整),如果存在则返回,不存在则继续。
    4、选择runid最小的从节点。在优先级和复制进度都相同的情况下,ID 号最小的从库得分最
    高,会被选为新主库。

  2. Leader对1选出来的从节点从节点执行slaveof noone命令让其成为主节点。

  3. Leader向剩余的从节点发送命令,让它们成为新主节点的从节点,复制规则和parallel-syncs参数有关。parallel-syncs就是用来限制在一次故障转移之后,每次向新的主节点发起复制操作的从节点个数。

  4. Leader将原来的主节点更新为从节点,并保持着对其关注,当其恢复后命令它去复制新的主节点

通知

通知从库和客户端。

问题

在主从复制阶段,写请求会失败,失败持续的时间 = 哨兵切换主从的时间 + 客户端感知到新主库 的时间。
如果不想让业务感知到异常,客户端只能把写失败的请求**先缓存起来或写入消息队列中间件**中,
等哨兵切换完主从后,再把这些写请求发给新的主库,但这种场景只适合对写入请求返回值不敏感的业务

当哨兵完成主从切换后,客户端需要及时感知到主库发生了变更,然后把缓存的写请求写入到新库中,
保证后续写请求不会再受到影响。
客户端也需要支持主动去获取最新主从的地址进行访问。
所以,客户端需要访问主从库时,不能直接写死主从库的地址了,而是需要从哨兵集群中获取最新的地址
(sentinel get-master-addr-by-name命令),这样当实例异常时,哨兵切换后或者客户端断开重连,都可以
从哨兵集群中拿到最新的实例地址。




哨兵实例(集群)是不是越多越好?
并不是,我们也看到了,哨兵在判定“主观下线”和选举“哨兵领导者”时,都需要和其他节点进行通信,
交换信息,哨兵实例越多,通信的次数也就越多,而且部署多个哨兵时,会分布在不同机器上,节点越多
带来的机器故障风险也会越大,这些问题都会影响到哨兵的通信和选举,出问题时也就意味着选举时间会变
长,切换主从的时间变久。


调大down-after-milliseconds值,对减少误判是不是有好处?
是有好处的,适当调大down-after-milliseconds值,当哨兵与主库之间网络存在短时波动时,可以降低误判的
概率。但是调大down-after-milliseconds值也意味着主从切换的时间会变长,对业务的影响时间越久,需要根
据实际场景进行权衡,设置合理的阈值。

评论