1 背景
从第三篇 《高可用之主从架构》,我们知道,为Redis配置主从模式,可以大幅度的提高Redis服务的可用性,减少甚至避免Redis服务发生宕机的可能。
它有如下能力:
- 故障隔离和恢复:无论主节点或者从节点宕机,其他节点依然可以保证服务的正常运行,并可以手动切换主从。
- 读写隔离:Master 节点提供写服务,Slave 节点提供读服务,分摊流量压力,均衡流量的负载。
- 提供高可用保障:主从模式是高可用的最基础版本,也是哨兵模式和 cluster模式实施的前置条件。
但是依然存在不小的问题,我们知道,在衡量系统可用性这边有个指标叫做MTTR,即平均修复时间。虽然主从模式支持手动切换,但是我们从知道服务故障到手动切换止损到恢复,这可能是一个比较长的过程。这期间的损失将难以计量,对于超高并发大系统是一个绝对灾难。所以我们需要系统自动的感知到Master故障,并选择一个 Slave 切换为 Master,实现故障自动转移的能力。
平均修复时间(Mean time to repair,MTTR),是描述产品由故障状态转为工作状态时修理时间的平均值。
2 什么是哨兵模式
在实际生产环境中,服务器难免会遇到一些突发状况:服务器宕机,停电,硬件损坏等等,一旦发生,后果不堪设想。
哨兵模式的核心还是主从模式的演变,只不过相对于主从模式在主节点宕机导致不可写的情况下,多了探活,以及竞选机制:从所有的从节点竞选出新的主节点,然后自动切换。竞选机制的实现,是依赖于在系统中启动sentinel进程,对各个服务器进行监控。如下图所示:
3 哨兵模式的主要职责
我们知道,要让Redis服务实现故障自动切换会有很多细节需要考虑,比如:
- 判定节点故障的条件是什么,有没有可能是假死或者响应延迟。
- 既然是竞选机制,那么所有slave节点都可以参与竞争,也都有机会成为master。选择哪个slave成为master是关键。
- 竞选出新的master,其他slave需要从新的master中replicaof,所以消息通知和通信也是核心。
带着这些思考,我们来看看官方对Redis哨兵的定义:
哨兵作为 Redis 的一种运行模式,专注于对 Redis 实例(master、slaves)运行状态进行监控,并能够在主节点发生故障时通过一系列的操作,实现新的master竞选、主从切换、故障转移,确保整个 Redis 服务的可用性。
所以,哨兵的能力至少应该包含如下几点:
- 监控:持续监控 master 、slave 是否健康,是否处于预期工作状态。
- 主从动态切换:当 Master 运行故障,哨兵启动自动故障恢复流程:从 slave 中选择一台作为新 master。
- 通知机制:竞选出新的master之后,通知客户端与新 master 建立连接;slave 从新的 master 中 replicaof,保障主从数据的一致性。
接下来我们一个个来看这几个能力的实现过程。
3.1 监控能力
哨兵模式启用的时候,会同步启用叫做Sentinel的进程。sentinel程会向所有的master 和 slaves 以及其他sentinel进程 发送心跳包(1s一次),看看是否正常返回响应。
- 如果slave 没有在规定的时间内响应 sentinel 的 PING 命令 , sentinel 会认为该实例已经挂了,将它tag为:下线状态;
- 同理,如果master 没有在规定时间响应 sentinel 的 PING 命令,也会被判定为 offline 状态,只是会多做一步 自动切换 master 的流程。
PING 命令的回复有两种情况:
- 有效回复:返回 +PONG、-LOADING、-MASTERDOWN 任何一种;
- 无效回复:有效回复之外的回复,或者指定时间内返回任何回复。
但是可能存在一些误判的情况,比如说网络拥塞、master实例假死、请求延迟,导致实例在某个短暂时间段不可用,后续又快速恢复了。
如果这时候被我们主动下线了,其实整个系统的可用性反而遭到了退化。而且 误判之后的一系列操作,master竞选、消息通知,slave 与新 master 同步数据,都会消耗大量资源。所以,误判要不得啊。
为了保证判断的可靠性,我们对下线的标识做了区分:一种是 主观下线,一种是客观下线。
- 主观下线
哨兵利用 PING 命令来监测 master、 slave 实例节点的生命状态。如果是无效回复,哨兵就把这个实例节点标记为 主观下线 。如果是slave,一般是有多从概念,直接下线即可,但如果是master,就要小心了。一个人sentinel容易误判,那就多个sentinel进投票裁决。哨兵机制就是这样的,采用多个实例组成sentinel集群模式进行部署,即哨兵集群。多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。
同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。
- 客观下线
master 是否要下线不能是单个sentinel能够决定的,上面说了我们一帮会有个sentinel集群 ,所以这个集群就发挥作用了,大家一起投票,超过一半的sentinel 都判断了 主观下线 ,这时候我们就把 master 标记为 客观下线,认为它是真的不行了。
当 master 被判定为 客观下线 后,就算正式没有master了,当务之急就是赶紧竞选出一个新的master。
- 如何区别主、客观下线
主观下线是sentinel自己认为节点offline,这时候节点并不是真正的下线;而客观下线是达到一定数量的哨兵(比如超过一半)都认为节点offline了,这时候会进一步触发离线、重新竞选主等一系列操作。
这里的「一定数量」是一个法定数量(Quorum),是由哨兵监控配置决定的,解释一下该配置:
# sentinel monitor # 举例如下: sentinel monitor mymaster 127.0.0.1 6379 2
这条配置项用于告知哨兵需要监听的主节点:
- sentinel monitor:代表监控。
- mymaster:代表主节点的名称,可以自定义。
- 192.168.11.128:代表监控的主节点 ip,6379 代表端口。
- 2:法定数量,代表只有两个或两个以上的哨兵认为主节点不可用的时候,才会把 master 设置为客观下线状态,然后进行 failover 操作。
客观下线 的标准就是,当有 N 个哨兵实例时,要有 N/2 + 1 个实例判断 master 为 主观下线 ,才能最终判定 master 为 客观下线 ,其实就是过半机制。
3.2 主从动态切换
sentinel 的一个很重要工作,就是从多个slave中选举出一个新的master。当然,这个选举的过程会比较严谨,需要通过 筛选 + 综合评估 方式进行选举,
3.2.1 筛选
- 过滤掉不健康的(下线或者断线),没有回复哨兵ping响应的从节点。
- 评估实例过往的网络连接状况
down-after-milliseconds,如果一定周期内(如24h)从库和主库经常断连,而且超出了一定的阈值(如 10 次),则该slave不予考虑。
这样,就保留下比较健康的实例了。
3.2.2 综合评估
筛选掉不健康的实例之后,我们就可以对于剩下健康的实例按顺序进行综合评估了。
- slave 优先级,通过 slave-priority 配置项(redis.conf),可以给不同的从库设置不同优先级,优先级高的优先成为master。
- 选择数据偏移量差距最小的,即slave_repl_offset与 master_repl_offset进度差距,其实就是比较 slave 与 原master 复制进度差距。
- slave runID,在优先级和复制进度都相同的情况下,选用runID最好的,runID越小说明创建时间越早,优先选为master。先来后到原则。
等这几个条件都评估完,我们就会选择出最适合slave,把他推举为新的master。
3.3 信息通知
等推选出最新的master之后,后续所有的写操作都会进入这个master中。所以需要尽快通知到所有的slave,让他们重新 replacaof 到 master上,重新建立runID和slave_repl_offset ,来保证数据的正常传输和主从一致性。如下图所示:
4 关于哨兵集群
前面说过了,单个哨兵对redis实例的离线判断可能会有误判,所以会有一个sentinel集群的概念,超过一定比例的sentinel(比如 > 1/2)的判断为主观下线,才能形成实质的客观下线。
那这边有几个知识点我们需要梳理清楚。
4.1 集群中的哨兵如何实现通信
使用redis的pub/sub 订阅能力实现哨兵间通信 和 slave 发现。
哨兵之间可以相互通信,主要归功于 Redis 的 pub/sub 发布/订阅机制。哨兵与 master 建立通信之后,可以利用 master 提供发布/订阅机制发布自己的IP、port等信息
master 有一个 sentinel:hello 的专用通道,用于哨兵之间发布和订阅消息。哨兵们都可以通过该通道发布自己的Name、IP、Port消息,同时订阅其他哨兵发布的Name、IP、Port消息。互相发现之后建立起了连接,后续的消息通信就可以直接进行了。
★这个与微服务中的服务注册与发现,以及RPC通信类似的整套做法类似。
4.2 哨兵如何与slave实现连接
- sentinel向master发送
INFO 命令
- master返回与之关联的slave 列表
- sentinel 根据 master 返回的 slave 列表,逐个与 salve 建立连接,并且根据这个连接持续监控
4.3 哨兵如何与客户端进行事件通知
依旧是通过 pub/sub 机制,发布不同事件,让客户端在这里订阅消息。客户端可以订阅哨兵的消息,哨兵提供的消息订阅频道有很多,不同频道包含了主从库切换过程中的不同关键事件。
5 总结
5.1 哨兵主要任务
Redis 哨兵机制是实现 Redis 不间断服务的高可用手段之一。主从架构集群的数据同步,是数据可靠的基础保障;主库宕机,自动执行主从切换是服务不间断的关键支撑。
Redis 哨兵机制实现了主从库的自动切换,再也不怕跟女盆友么么哒的时候 master 宕机了:
- 监控 master 与 slave 运行状态,判断是否客观下线;
- master 客观下线后,选择一个 slave 切换成 master;
- 通知 slave 和客户端新 master 信息。
5.2 哨兵集群原理
为了避免单个哨兵故障后无法进行主从切换,以及为了减少误判率,又引入了哨兵集群;哨兵集群又需要有一些机制来支撑它的正常运行:
- 基于 pub/sub 机制实现哨兵集群之间的通信;
- 基于 INFO 命令获取 slave 列表,帮助 哨兵与 slave 建立连接;
- 通过哨兵的 pub/sub,实现了与客户端和哨兵之间的事件通知。
主从切换,并不是随意选择一个哨兵就可以执行,而是通过投票仲裁,选择一个 Leader,由这个 Leader 负责主从切换。