跳至主要內容

哨兵机制:主库挂了怎么办

AruNi_Lu数据库Redis约 2849 字大约 10 分钟

本文内容

1. 主库挂了怎么办?

在主从库集群模式下,如果从库发生故障,客户端还可以向主库和其他从库发送请求,不会影响到可用性。但如果主库发生故障,就会影响从库的数据同步,以及写操作无法进行了,因为主从模式采用的是读写分离。

从库无法同步数据,写操作无法执行,这两个都是不能接受的,严重影响到了集群的可用性

因此,当主库挂了后,就需要重新来一个新主库,比如把一个从库切换为主库。

不过,重新选择一个主库并不是那么简单的,还需要考虑三个问题:

  • 主库是真的挂了吗
  • 选择哪个从库作为主库
  • 怎么把新主库通知给客户端和其他从库

Redis 的 哨兵机制 就有效的解决了主从模式下故障转移的上面三个问题,下面就来看看哨兵机制到底是怎么一回事。

2. 哨兵机制是什么?

与主从库实例一样,哨兵也是一个特殊的 Redis 进程,它主要负责三个任务:监控、选主、通知

这三个任务的主要目标如下图所示:

img

2.1 监控

监控 就是指哨兵进程在运行时,会周期性地给所有主从库发送 PING 命令,检查它们是否在线运行,如果它们 没有在规定时间内响应,哨兵就会把它们标记为 “下线状态”。

不过,对于 主库 来说,判断下线状态没这么简单,涉及到 主观下线和客观下线。而对于从库来说,哨兵只需要标记为 “主观下线” 就行了,因为从库下线影响不大,不影响集群的可用性。

为什么主库需要两个下线状态?

对于主库来说,如果只是简单的标记为 “主观下线”,就开启主从切换(选一个从库切换成主库),会有一个 误判情况,就是其实 主库并没有故障,只是 集群的网络拥塞、哨兵本身网络不好、或者是主库自身压力比较大,响应慢了 等情况。

如果出现了上面的误判情况后开启主从切换,后续的选主和通知操作都会带来额外的开销,比如花时间选出新主库、重新给从库同步数据。而这些开销实际上是不需要的,因为主库其实并没有挂,只是哨兵自己误判了。

想要减少误判,一个常用的做法就是大家协商,多数人都认为它挂了,才是真的挂了,即客观下线。

哨兵机制就是这样,哨兵通常也会部署多个,它们被称为 哨兵集群。在判断主库是否下线时,只有大多数哨兵实例都判断主库已经 “主观下线” 后,主库才会被标记为 “客观下线”,判断原则就是 少数服从多数

简单来说,“客观下线” 的标准就是有 N/2 + 1 个实例都认为主库已经 “主观下线” 了,所以一般将哨兵实例个数设置成基数个,比如 3、5、7。

引入多个哨兵实例一起判断,就可以 避免单个哨兵因为自身网络不好导致的误判,这样就能降低误判率。

2.2 选主

哨兵在选主时需要决定选择哪个从库作为新的主库,选择过程可以分为 筛选 + 打分,即从多个从库中先按照 一定的筛选条件,把不合符的从库去掉,然后再按照 一定规则打分,得分最高的即为新的主库。

先来看看 筛选条件,要确保所选的 从库任然在线运行,这个在线状态还需要判断 其之前的网络连接状态避免从库经常和主库断连 的情况。

具体判断需要使用配置项 down-after-milliseconds,它表示 主从库断连的最大超时时间,如果在 down-after-milliseconds 毫秒内,主从库都还没连接上,则可以认为主从库断连了,如果发生断连的次数超过了 10 次,就说明该从库的网络状况不好,不适合作为新主库

再来看看如何 打分,有三个打分规则对从库依次进行三轮打分,这三个规则分别是 从库优先级、从库复制进度和从库 ID 号。这个打分过程中,只要某一轮中有一个从库得分最高,那它就是新主库,结束了选主过程。出现了 平分 的情况,才会继续进行 下一轮打分

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

我们可以通过 slave-priority 配置项给从库设置优先级,比如可以给内存、性能高的从库设置一个高优先级,在选主时让其得分最高,成为新主库。

如果从库的优先级都一样,哨兵就会开启第二轮打分。

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

这里就涉及到 主从模式open in new window 中讲的,主从库同步增量命令时有个基于长连接命令传播的过程,这时 主库和从库都会有一个 repl_backlog_buffer 环形缓冲区,用于断连后增量恢复使用。主库会记录自己写到的位置,从库则会记录自己读到的位置,它们之间的差值就代表了主从库的同步进度

也就是说,哪个从库 repl_backlog_buffer 读到的位置与主库 repl_backlog_buffer 写到的位置越接近,哪个从库的得分就越高。如果有多个从库的位置都相同,那就要进行第三轮打分了。

其实这里的比较同步进度,实际上是比较从库之间的读取位置,因为此时主库已经挂了,是不知道主库写到的位置的。

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

每个 Redis 实例都有一个 ID,类似与从库的编号,ID 号小的从库得分高,因为 ID 号小就意味着该从库存在的时间更长,也就能代表其更稳定

最后再来总结以下选主的过程:首先哨兵会根据 在线状态、网络状态 筛选掉一部分从库,然后按照 从库优先级、复制进度、ID 号大小 进行 打分只要有得分最高的从库出现,其就是新主库

2.3 通知

通知比较简单,哨兵只需要把新主库的连接信息发送给其他从库,让从库们执行 replicaof 命令,重新和新主库建立连接,然后同步数据。

同时,哨兵也会把新主库的连接信息通知给客户端,这样客户端就可以把后续请求发给新主库了。

3. 哨兵在主从切换过程中,客户端还能正常请求吗?

通过上面的分析可知,哨兵在主从切换过程中涉及选主、通知,可能会消耗比较长的时间,那这个过程客户端还能正常请求吗?

如果客户端使用了读写分离,那么读请求还是可以在从库上正常执行的,但是 写请求会失败持续的时间哨兵主从切换的时间 + 客户端感知到新主库的时间

想要 减少写请求失败的持续时间,可以通过配置 down-after-milliseconds 参数来变相实现,上面讲过,该参数表示主从库断连的最大超时时间,对于哨兵来说,表示 认为主库断连的超时时间,配置的越短,哨兵就能越早发现主库断连,从而更早的发起主从切换,以切换的更及时。但也有可能 因为网络拥堵主库正常而导致的不必要切换,所以需要综合考虑。

另外,要 让客户端尽快感知到主库发生了变化,做法如下:

  • 对于哨兵来说,哨兵选出新主库后,就会把新主库地址写入 topic 为 switch-master 的 pubsub 中,客户端需要订阅这个 pubsub,以及时感知主库发生了变化,并拿到新主库的地址。这种机制属于哨兵主动通知客户端。

  • 对于客户端来说,需要主动地去获取最新地主从库地址进行访问,所以 不能直接写死主从库的地址,而是要 从哨兵集群中获取,可以通过 sentinel get-master-addr-by-name 命令。这样当哨兵切换主库或者客户端断开重连后,客户端都可以从哨兵集群中获取最新的主从库地址。

    一般 Redis 的 SDK 都提供了通过哨兵拿到实例地址,再访问实例的方式,直接使用即可。在分片集群(后续讲解)模式下,这些逻辑都可以在 proxy 层做,客户端只需要正常请求 proxy 服务器即可。

4. 哨兵集群判断主库客观下线后,由哪个哨兵来执行主从切换?

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

至于如何选出这个 “哨兵领导者”,就涉及到 分布式系统中的选举/共识算法了,可以看看分布式选举 Leader 的文章 分布式选举open in new window。常见的有 Paxos、Raft 算法,哨兵集群采用的就是类似与 Raft 的共识算法。

简单来说,每个哨兵会设置一个随机超时时间,超时后每个哨兵会请求其他哨兵为自己投票,其他哨兵会对收到的第一个请求进行投票,而且每轮只能投一票,投票结束后,具有多数票(超过哨兵节点数的一半,且达到配置的 quorum 值)的哨兵会成为 “哨兵领导者”,如果这一轮投票没有达到多数票的哨兵,就会重新选举,直到选出这个领导者。

5. 参考文章

  • 《Redis 核心技术与实战》
上次编辑于: