Redis学习记录 07 Redis的哨兵机制

前言

在主从库集群模式下,如果从库故障,主库可以继续工作;但是如果主库挂了,会影响到从库的数据同步。

如果客户端是读请求,从库还可以继续响应,但是如果是写请求,由于Redis的读写分离机制,因此没有实例可以响应客户端的写请求。如图

 

图1 主库故障后从库无法服务写操作

 

无论是无法数据同步,还是无法响应写请求,这都是不可接受的。因此,在主库挂了以后,需要有一个机制来保证选择一个新的主库。

这个机制需要解决以下三个问题:

  1. 主库真的挂了吗?
  2. 该选择哪个库作为主库?
  3. 怎么把新主库的信息同步给从库和客户端?

哨兵机制就是为了解决这些问题的,可以实现主从库的自动切换。

 

 

哨兵机制的基本流程

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

 

如图所示:

 

图2 哨兵机制的三项任务与目标

 

监控

 

在监控任务中,哨兵需要判断主库是否处于下线状态。哨兵对主库的下线判断有“主观下线”和“客观下线”两种。

主观下线和客观下线

哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状态。如果哨兵发现主库或从库对 PING 命令的响应超时了,那么,哨兵就会先把它标记为“主观下线”。 

如果检测的是从库,那么,哨兵简单地把它标记为“主观下线”就行了,因为从库的下线影响一般不太大,集群的对外服务不会间断。

但是,如果检测的是主库,那么,哨兵还不能简单地把它标记为“主观下线”,开启主从切换。因为很有可能存在这么一个情况:那就是哨兵误判了,其实主库并没有故障。可是,一旦启动了主从切换,后续的选主和通知操作都会带来额外的计算和通信开销。

如何减少误判呢?

哨兵机制通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。

在判断主库是否下线时,不能由一个哨兵说了算,只有大多数的哨兵实例,都判断主库已经“主观下线”了,主库才会被标记为“客观下线”。

如图:

 

图3 客观下线的判断

 

 

简单来说,“客观下线”的标准就是,当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线”。这样一来,就可以减少误判的概率,也能避免误判带来的无谓的主从库切换。(当然,有多少个实例做出“主观下线”的判断才可以,可以由 Redis 管理员自行设定)。

原则就是少数服从多数。

 

 

选主

如何选定新主库? 哨兵选择新主库的过程称为“筛选 + 打分”。简单来说,我们在多个从库中,先按照一定的筛选条件,把不符合条件的从库去掉。然后,我们再按照一定的规则,给剩下的从库逐个打分,将得分最高的从库选为新主库,如下图所示:

图4  新主库的选择过程

 

筛选条件

在选主时,除了要检查从库的当前在线状态,还要判断它之前的网络连接状态。如果从库总是和主库断连,而且断连次数超出了一定的阈值,我们就有理由相信,这个从库的网络状况并不是太好,就可以把这个从库筛掉了。

具体怎么判断呢?

使用配置项 down-after-milliseconds * 10。其中,down-after-milliseconds 是我们认定主从库断连的最大连接超时时间。如果在 down-after-milliseconds 毫秒内,主从节点都没有通过网络联系上,我们就可以认为主从节点断连了。如果发生断连的次数超过了 10 次,就说明这个从库的网络状况不好,不适合作为新主库。

这样,我们就过滤掉了不适合做主库的从库,完成了筛选工作。

 

打分规则

筛选结束以后,分别按照三个规则依次进行三轮打分,这三个规则分别是从库优先级、从库复制进度以及从库 ID 号只要在某一轮中,有从库得分最高,那么它就是主库了,选主过程到此结束。如果没有出现得分最高的从库,那么就继续进行下一轮。

第一轮:优先级最高的从库得分高。

用户可以通过 slave-priority 配置项,给不同的从库设置不同优先级。

第二轮:和旧主库同步程度最接近的从库得分高。

主从库同步时有个命令传播的过程。在这个过程中,主库会用 master_repl_offset 记录当前的最新写操作在 repl_backlog_buffer 中的位置,而从库会用 slave_repl_offset 这个值记录当前的复制进度。

它的 slave_repl_offset 需要最接近 master_repl_offset。如果在所有从库中,有从库的 slave_repl_offset 最接近 master_repl_offset,那么它的得分就最高,可以作为新主库。

第三轮:ID 号小的从库得分高。

每个实例都会有一个 ID,这个 ID 就类似于这里的从库的编号。目前,Redis 在选主库时,有一个默认的规定:在优先级和复制进度都相同的情况下,ID 号最小的从库得分最高,会被选为新主库。

 

 

通知

在这三个任务中,通知任务相对来说比较简单,哨兵只需要把新主库信息发给从库和客户端,让它们和新主库建立连接就行,并不涉及决策的逻辑。

 

 

小结

一、作者讲了什么?

Redis故障转移:主从切换机制哨兵


二、作者是怎么把这事给讲明白的?

    1,提出主从切换的三个问题:a,主机状态确认 b,新主库选举 c,新主库通知
    2,讲解了哨兵的本质是一个特殊的redis进程(实例),有三个职责:监控,选主,通知


三、为了讲明白,作者讲了哪些要点,有哪些亮点?

    1,亮点1:哨兵的本质:是一个redis实例,要做三件事:监控主库,选举新主库,通知客户端和从机(这让我对哨兵理解清晰了很多)
    2,要点1:哨兵是通过心跳检测,监控主库状态,主库下线被分为:主观下线和客观下线、
    3,要点2:哨兵监控是可能误判的,所以哨兵一般是集群部署,采取投票的形式减少误判
    4,要点3:选定新主库规则是先筛选在打分,得分高的会被选为新主库,
    5,要点4:筛选规则:从库当前的网络连接状况,以及之前的网络连接状况,筛选中断次数标准可以配置
    6,要点5:打分规则:从库的优先级,数据同步状况,Id号大小,可以分为三轮,只要有一轮出现得分高的,就能选出


    
四、留言区的收获
    1,数据同步状况的判断:
            a:判断哪个从库的数据同步最接近主库,不是拿从库与主库比较,而是从库之间互相比较,谁大谁就是最接近的
            b:这样做的原因有二:主库已下线无法获取主库信息,环形缓冲区的位置偏移量是单调递增的(主库的被称为:master_repl_offset,从库的被称为:slave_repl_offset,其实两者本质是相同的,叫不同的名字只是为了区分)
    2,哨兵的使用:(感谢 @Kaito 大神简洁明了,无私的分享)
            a:主库下线,可读不可写,写失败的时间=哨兵切换主从的时间+客户端感知新主库时间
            b:主库下线无感知,需要客户端与哨兵配合改造:
                      1:哨兵主动通知:哨兵需要将最新的主库地址写入自己的pubsub中,客户端需要订阅这个pubsub,当这个pubsub有数据时,客户端就能感知到
                      2:客户端主动获取:客户端不将主从库写死,而是从哨兵集群中获取,从而始终获取最新的主从地址
            c:集群分片模式的Redis集群,可以不使用哨兵机制

 

 

疑问

1、通过哨兵机制,可以实现主从库的自动切换,这是实现服务不间断的关键支撑,同时,我也提到了主从库切换是需要一定时间的。所以,请你考虑下,在这个切换过程中,客户端能否正常地进行请求操作呢?如果想要应用程序不感知服务的中断,还需要哨兵或需要客户端再做些什么吗?

答:如果客户端使用了读写分离,那么读请求可以在从库上正常执行,不会受到影响。但是由于此时主库已经挂了,而且哨兵还没有选出新的主库,所以在这期间写请求会失败,失败持续的时间 = 哨兵切换主从的时间 + 客户端感知到新主库 的时间。


如果不想让业务感知到异常,客户端只能把写失败的请求先缓存起来或写入消息队列中间件中,等哨兵切换完主从后,再把这些写请求发给新的主库,但这种场景只适合对写入请求返回值不敏感的业务,而且还需要业务层做适配,另外主从切换时间过长,也会导致客户端或消息队列中间件缓存写请求过多,切换完成之后重放这些请求的时间变长。

哨兵检测主库多久没有响应就提升从库为新的主库,这个时间是可以配置的(down-after-milliseconds参数)。配置的时间越短,哨兵越敏感,哨兵集群认为主库在短时间内连不上就会发起主从切换,这种配置很可能因为网络拥塞但主库正常而发生不必要的切换,当然,当主库真正故障时,因为切换得及时,对业务的影响最小。如果配置的时间比较长,哨兵越保守,这种情况可以减少哨兵误判的概率,但是主库故障发生时,业务写失败的时间也会比较久,缓存写请求数据量越多。

应用程序不感知服务的中断,还需要哨兵和客户端做些什么?当哨兵完成主从切换后,客户端需要及时感知到主库发生了变更,然后把缓存的写请求写入到新库中,保证后续写请求不会再受到影响,具体做法如下:

哨兵提升一个从库为新主库后,哨兵会把新主库的地址写入自己实例的pubsub(switch-master)中。客户端需要订阅这个pubsub,当这个pubsub有数据时,客户端就能感知到主库发生变更,同时可以拿到最新的主库地址,然后把写请求写到这个新主库即可,这种机制属于哨兵主动通知客户端。

如果客户端因为某些原因错过了哨兵的通知,或者哨兵通知后客户端处理失败了,安全起见,客户端也需要支持主动去获取最新主从的地址进行访问。

所以,客户端需要访问主从库时,不能直接写死主从库的地址了,而是需要从哨兵集群中获取最新的地址(sentinel get-master-addr-by-name命令),这样当实例异常时,哨兵切换后或者客户端断开重连,都可以从哨兵集群中拿到最新的实例地址。

一般Redis的SDK都提供了通过哨兵拿到实例地址,再访问实例的方式,我们直接使用即可,不需要自己实现这些逻辑。当然,对于只有主从实例的情况,客户端需要和哨兵配合使用,而在分片集群模式下,这些逻辑都可以做在proxy层,这样客户端也不需要关心这些逻辑了,Codis就是这么做的。

 


2、哨兵集群中有实例挂了,怎么办,会影响主库状态判断和选主吗?

答:这个属于分布式系统领域的问题了,指的是在分布式系统中,如果存在故障节点,整个集群是否还可以提供服务?而且提供的服务是正确的?

这是一个分布式系统容错问题,这方面最著名的就是分布式领域中的“拜占庭将军”问题了,“拜占庭将军问题”不仅解决了容错问题,还可以解决错误节点的问题,虽然比较复杂,但还是值得研究的,有兴趣的同学可以去了解下。

简单说结论:存在故障节点时,只要集群中大多数节点状态正常,集群依旧可以对外提供服务。具体推导过程细节很多,大家去查前面的资料了解就好。



3、哨兵集群多数实例达成共识,判断出主库“客观下线”后,由哪个实例来执行主从切换呢?

答:哨兵集群判断出主库“主观下线”后,会选出一个“哨兵领导者”,之后整个过程由它来完成主从切换。

但是如何选出“哨兵领导者”?这个问题也是一个分布式系统中的问题,就是我们经常听说的共识算法,指的是集群中多个节点如何就一个问题达成共识。共识算法有很多种,例如Paxos、Raft,这里哨兵集群采用的类似于Raft的共识算法。

简单来说就是每个哨兵设置一个随机超时时间,超时后每个哨兵会请求其他哨兵为自己投票,其他哨兵节点对收到的第一个请求进行投票确认,一轮投票下来后,首先达到多数选票的哨兵节点成为“哨兵领导者”,如果没有达到多数选票的哨兵节点,那么会重新选举,直到能够成功选出“哨兵领导者”。

 

posted @ 2022-02-08 16:18  r1-12king  阅读(180)  评论(0编辑  收藏  举报