Redis哨兵机制

Redis多机数据库-哨兵机制

主从库的集群模式使得当从库发生故障以后,客户端可以继续向主库或者其他从库发送请求,进行相关的操作;但是如果主库发生了故障,那会直接影响到从库的同步。无论是写中断还是从库无法进行数据同步都是Redis所不能接受的。因此我们需要一些机制,来能够将一个从库切换为主库,这就涉及到了Redis的哨兵机制。

1. 哨兵机制的基本流程

  • 哨兵可以理解为一个运行在特殊模式下的Redis进程,其在主从库实例运行的同时也在运行
  • 哨兵主要的三个任务为:
    • 监控 — 决策:判断主库是否处于下线状态
      • 周期性的ping主库,检测其是否仍然在线运行
      • 如果从库没有在规定时间内响应哨兵的Ping命令,哨兵就会将其标记为下线状态
      • 对主库来说同理,在判定主库下线以后会开始一个自动切换主库的流程
    • 选主 — 决策:决定选择哪个从库实例作为主库
      • 主库挂了以后,哨兵就需要从很多从库里按照一定的规则选择一个从库实例,将其作为新的主库
    • 通知
      • 将新主库的连接信息发给其他从库,让它们执行replicaof命令,与新主库建立连接,并进行数据复制
      • 哨兵会将新主库的连接信息通知给客户端,让它们将请求操作发到新主库当中

哨兵机制任务

哨兵三大任务

2. 判断主库的下线状态

2.1 哨兵集群使用原因

2.1.1 为什么需要哨兵集群?

  • 如果哨兵发生误判,后续的选主和通知操作都会带来额外的计算和通信的开销
  • 误判通常发生在
    • 集群网络压力较大
    • 网络拥塞
    • 主库本身压力较大的情况
  • 哨兵机制也是类似的,它通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。

2.1.2 如何使用哨兵集群?

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

2.2 哨兵集群原理 — 基于PubSub机制

2.2.1 pubsub机制

哨兵实例之间的相互发现是基于Redis提供的pubsub机制的,哨兵只要和主库建立起连接,就可以在主库上发布消息了

  • 可以选择发布自己的连接信息到主库上
  • 也可以从主库上订阅消息,获得其他哨兵发布的连接信息
  • 当多个哨兵实例都在主库上做了发布和订阅操作之后,他们之间就能知道彼此的IP地址和端口

2.2.2 频道

  • Redis通过频道来区分不同应用的消息,对这些消息进行分门别类的管理。频道就是指消息的类别,当消息类别相同时,就会属于同一个频道,否则属于不同的频道。
  • 主库会构建一个叫做 __sentinel__:hello 的频道,来让各个哨兵互相发现
  • 只有订阅了同一个频道的应用,才能通过发布的消息进行信息交换

pubsub机制

频道订阅机制

  • 哨兵1 向频道hello发送信息,因为哨兵2 哨兵3 subscribe了hello频道,他们就能从这个频道获取到哨兵1的IP地址和端口号信息

2.2.3 哨兵和从库的连接沟通

  • 哨兵向主库发出INFO命令

  • 主库收到命令后,就会将从库列表返回给哨兵

  • 接着哨兵就可以根据从库列表中的信息,和每个从库建立连接,并在这个连接上持续对从库进行监控

    哨兵与从库之间的连接

    哨兵和从库的连接

  • 哨兵除了上述的和主库之间的连接,获取从库列表,并和从库们建立连接之外,还承担着在发生主库更换以后,将新主库的信息告诉客户端这个任务

2.3 客户端事件通知机制

  • 哨兵是一个运行在特定模式下的Redis实例,只是它不服务请求操作,只是完成监控,选主和通知的任务

  • 因此每个哨兵实例也提供pubsub机制,客户端可以从哨兵订阅消息

    • 哨兵提供了很多的消息订阅频道,不同频道包含了主从库切换过程中的不同关键事件

      哨兵常用的消息订阅频道

    • 客户端可以执行订阅命令,来订阅不同的频道,然后来获取不同的事件信息

      • 当哨兵将新的主库选出来以后,客户端会看到switch-master事件,事件中会包含新的主库的IP地址还有端口信息
      • 此时客户端就会使用新主库地址和端口来进行通信了

3. 如何选定新主库?

哨兵筛选新主库的过程称为筛选+打分

  • 筛选 — 按照一定条件筛选,将不符合条件的从库去掉
    • 确保从库仍然在线运行
    • 判断其之前的网络状态 看该从库和主库之间是否经常断联,出现网络相关的问题
      • 通过使用down-after-milliseconds属性,这是我们认为的最大主从间的连接超时,如果超过这个时间我们就认为断联了,超过一定次数就认为从库网络状况不好
  • 打分 — 只要有得分最高的,那么就在当前轮停止并且认定其为主库
    • 从库优先级 — 手动设置的
      • 用户可以通过slave-priority配置项,给不同的从库设置不同的优先级
        • 譬如:两个从库内存大小不一样,我们就可以手动给内存大的实例设置一个高优先级
    • 从库复制进度
      • 选择和旧主库同步最为接近的那个从库作为主库
      • 如何判断从库和旧主库的同步进度?
        • 主从库之间命令传播机制里面的master_repl_offset 和slave_repl_offset
        • 看二者的接近程度
    • 从库ID号
      • 当优先级和复制进度都相同的情况下,ID号最小的从库得分最高,被选为新主库

4. 由哪个哨兵来执行主从切换?

  • 任何一个实例只要自身判断主库主观下线之后,就会给其他势力发送is-master-down-by-addr命令。接着其他实例会根据自己和主库的连接情况,做出Y或N的响应

      ![哨兵沟通,确定主库是否下线](https://i.loli.net/2021/01/11/HpT5MAdKX9fmo2S.png)
    
      is master down by addr
    • 一个哨兵获得了仲裁所需的赞成票数后,就可以标记主库为客观下线

      • 这个所需的赞成票数是通过哨兵配置文件中的quorum配置项设定的
    • 当获得了所需赞成票数以后,这个哨兵会再给其他哨兵发送命令,希望由自己来执行主从切换,并让所有其他哨兵进行投票,这个过程称为Leader选举

    • 在投票过程中,任何一个想成为 Leader 的哨兵,要满足两个条件:

      • 第一,拿到半数以上的赞成票;
      • 第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。以 3 个哨兵为例,假设此时的 quorum 设置为 2,那么,任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以了。

      哨兵投票,选举leader

      票选执行主从切换哨兵的过程

    1. 在 T1 时刻,S1 判断主库为“客观下线”,它想成为 Leader,就先给自己投一张赞成票,然后分别向 S2 和 S3 发送命令,表示要成为 Leader。
    2. 在 T2 时刻,S3 判断主库为“客观下线”,它也想成为 Leader,所以也先给自己投一张赞成票,再分别向 S1 和 S2 发送命令,表示要成为 Leader。
    3. 在 T3 时刻,S1 收到了 S3 的 Leader 投票请求。因为 S1 已经给自己投了一票 Y,所以它不能再给其他哨兵投赞成票了,所以 S1 回复 N 表示不同意。同时,S2 收到了 T2 时 S3 发送的 Leader 投票请求。因为 S2 之前没有投过票,它会给第一个向它发送投票请求的哨兵回复 Y,给后续再发送投票请求的哨兵回复 N,所以,在 T3 时,S2 回复 S3,同意 S3 成为 Leader。
    4. 在 T4 时刻,S2 才收到 T1 时 S1 发送的投票命令。因为 S2 已经在 T3 时同意了 S3 的投票请求,此时,S2 给 S1 回复 N,表示不同意 S1 成为 Leader。发生这种情况,是因为 S3 和 S2 之间的网络传输正常,而 S1 和 S2 之间的网络传输可能正好拥塞了,导致投票请求传输慢了。
    5. 在 T5 时刻,S1 得到的票数是来自它自己的一票 Y 和来自 S2 的一票 N。而 S3 除了自己的赞成票 Y 以外,还收到了来自 S2 的一票 Y。此时,S3 不仅获得了半数以上的 Leader 赞成票,也达到预设的 quorum 值(quorum 为 2),所以它最终成为了 Leader。接着,S3 会开始执行选主操作,而且在选定新主库后,会给其他从库和客户端通知新主库的信息。
    • 如果 S3 没有拿到 2 票 Y,那么这轮投票就不会产生 Leader。哨兵集群会等待一段时间(也就是哨兵故障转移超时时间的 2 倍),再重新选举。这是因为,哨兵集群能够进行成功投票,很大程度上依赖于选举命令的正常网络传播。如果网络压力较大或有短时堵塞,就可能导致没有一个哨兵能拿到半数以上的赞成票。所以,等到网络拥塞好转之后,再进行投票选举,成功的概率就会增加。需要注意的是,如果哨兵集群只有 2 个实例,此时,一个哨兵要想成为 Leader,必须获得 2 票,而不是 1 票。所以,如果有个哨兵挂掉了,那么,此时的集群是无法进行主从库切换的。因此,通常我们至少会配置 3 个哨兵实例。

      5. FAQs

      5.1 哨兵在操作主从切换的过程当中,客户端能否正常进行请求操作?

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

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

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

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

      • 哨兵提升一个从库为新主库后,哨兵会把新主库的地址写入自己实例的pubsub(switch-master)中。客户端需要订阅这个pubsub,当这个pubsub有数据时,客户端就能感知到主库发生变更,同时可以拿到最新的主库地址,然后把写请求写到这个新主库即可,这种机制属于哨兵主动通知客户端。
      • 如果客户端因为某些原因错过了哨兵的通知,或者哨兵通知后客户端处理失败了,安全起见,客户端也需要支持主动去获取最新主从的地址进行访问。
      • 所以,客户端需要访问主从库时,不能直接写死主从库的地址了,而是需要从哨兵集群中获取最新的地址(sentinel get-master-addr-by-name命令),这样当实例异常时,哨兵切换后或者客户端断开重连,都可以从哨兵集群中拿到最新的实例地址。
      • 一般Redis的SDK都提供了通过哨兵拿到实例地址,再访问实例的方式,我们直接使用即可,不需要自己实现这些逻辑。当然,对于只有主从实例的情况,客户端需要和哨兵配合使用,而在分片集群模式下,这些逻辑都可以做在proxy层,这样客户端也不需要关心这些逻辑了,Codis就是这么做的

      Reference

    1. 极客时间Redis课程
    2. Redis设计与实现

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com

文章标题:Redis哨兵机制

文章字数:3.8k

本文作者:Leilei Chen

发布时间:2021-07-06, 11:07:59

最后更新:2021-07-06, 03:22:25

原始链接:https://www.llchen60.com/Redis%E5%93%A8%E5%85%B5%E6%9C%BA%E5%88%B6/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏