通过之前的学习,你已知道 Redis 主从复制 是高可用的基石,某个 slave 宕机依然可以将请求发送给 master 或者其他 slave,但是如果 master 宕机,则只能响应读操作,写请求无法再执行。
所以主从复制架构面临一个严峻问题:master 宕机,无法执行写操作,无法自动选择将一个 slave 切换为 master,也就是无法实现自动故障切换。
“ Chaya:“还记得那晚我与男友约会,眼前是橡树的绿叶,白色的竹篱笆。好想告诉我的他,这里像幅画,一起手牵手么么哒(此处省略 10000 字)。
“Redis 忽然宕机,我的恋人小 Q 总不能把我推开,停止甜蜜,然后打开电脑手工进行主从切换,再通知其他程序员把地址改成新 master 的信息上线。”
如此一折腾,你心里的雨倾盆地下,万万使不得。所以必须有一个高可用的方案,为此,我提供一个高可用方案——哨兵(sentinel)。
哨兵是什么?
“ 吃瓜群众:“Redis 大佬,虽然我没有女朋友,但是未雨绸缪,我要掌握这个哨兵模式,防止当我与女朋友约会时被打扰,你快说说什么是哨兵以及哨兵的实现原理吧。”
先来看看哨兵是什么?搭建哨兵集群的方法我就不细说了,假设三个哨兵组成一个哨兵集群,三个数据节点构成一个一主两从的 Redis 主从架构,如图 3-17 所示。
图3-17
Redis 哨兵集群高可用架构有三种角色,分别是 master、slave 和 sentinel。
- sentinel 之间互相通信,组成一个集群实现哨兵高可用,选举出一个 leader 执行故障迁移操作。
- master 与 slave 之间通信,组成主从复制架构。
- sentinel 与 master/ slave 通信,是为了对该主从复制架构进行管理,包括监视(Monitoring)、通知(Notification)、自动故障切换(Automatic Failover)、配置提供者(Configuration Provider)。
哨兵监控的 master 的名字叫作 mymaster,master 的 IP 地址是 127.0.0.1,端口是 6379。quorum 是关键参数,它的作用如下。
- 指定在标记 master 故障并尝试执行故障切换时需要一定数量达成一致意见的哨兵进程。大白话就是需要多少个哨兵进程认为 master 宕机,真正标记 master 宕机才能启动故障切换过程。
- 对于多个哨兵,需要选出一个 leader 来执行实际的故障自动转移操作,当某个哨兵的票数超过 quorum 时,就选举这个哨兵为 leader,负责自动故障切换。quorum 的值一般取哨兵个数的一半以上 (n/2 + 1) 比较合理。
哨兵只要配置 master 信息即可与三个角色建立联系。
“ Chaya:“为什么哨兵只需要配置 master 信息就可以与三个角色建立联系?”
- 哨兵可以通过 master 获取 slave 的信息,并与 slave 建立连接。master 与 slave 是主从关系,通过 info 命令就可以通过 master 获取 slave 的 IP 地址 和 port、runid 等信息。
- 通过上面的步骤,哨兵与 master 和所有的 slave 建立连接,哨兵之间的互相感知则通过 Redis 的发布/订阅机制实现。每个哨兵通过发布/订阅 master 的 sentinel :hello 频道发布和接收信息,以此感知其他哨兵的存在并建立连接。
哨兵的任务
哨兵是 Redis 的一种运行模式,它专注于对 Redis 实例(master、slave)运行状态的监控,并能够在 master 发生故障时通过一系列的机制实现选主及主从切换,实现自动故障切换,确保整个 Redis 系统的可用性。
Chaya 可以安心地与爱人在欢乐港湾约会,尽情享受甜蜜,哪怕是吵架都那么醉人,不再需要担心 Redis 忽然宕机带来的烦恼。
我们先从全局看哨兵,简要地了解它的整个运作流程,接着针对每个任务详细分析,Redis 哨兵的主要职责如下。
- 监控(Monitoring) :Redis 的哨兵不断检查 master 和 slave 实例是否按预期工作。它监视实例的健康状态,包括 master 和所有 slave。
- 自动故障切换(Automatic Failover)**:如果 master 出现故障或不按预期工作,Redis 的哨兵则启动自动故障切换流程。在此过程中,一个 slave 会被晋升为新的 master。
- 通知(Notification) :让 slave 执行 replicaof 命令与新的 master 同步数据;并且通知客户端与新的 master 建立连接,如图 3-18 所示。
- 配置提供者(Configuration Provide) :哨兵充当了客户端服务发现的权威来源。客户端连接到任何一个哨兵以获取新的 master 的地址,确保能够连接到正确的实例。
- 监控
Redis :八卦一下,Chaya,你的恋人用什么方式来了解你每天的喜怒哀乐呢?
“ Chaya:“这很简单,他每天给我发微信消息、打电话或者打视频,若是哪天我不接电话,或者他发送微信消息时出现红色感叹号,就说明我把他拉黑了。”
哨兵与各个角色节点建立连接后,通过 PING、INFO、PUBLISH / SUBSCRIBE命令来监控所有实例的健康状态,当然,它不会说情话。
哨兵默认会以每秒一次的频率向所有的 master、slave、哨兵发送 PING 命令,这个其实是一个心跳检测,用于探测实例是否存活。
- PING:所有节点之间通过发送 PING 命令确认对方是否在线,默认每秒发送一次。
- I NFO:哨兵向 master、slave 发送该命令,用于获取 slave 的详细信息。
- PUBLISH / SUBSCRIBE:哨兵会订阅 master 和 slave 的 sentinel :hello 频道,并通过该频道发布自己的信息,这样其他哨兵之间就可以建立联系。
如果一个 master 实例距离最后一次有效回复 PING 命令的时间超过 down-after- milliseconds 选项所指定的值,这个 master 实例就会被哨兵标记为“主观下线”。
如果 slave 没有在指定时间内响应哨兵的 PING 命令,则直接被标记为“主观下线”。
只有当大于或等于法定个数(quorum)的哨兵节点认为该 master 主观下线时,才能将该 master 改为客观下线。接着才会开启自动故障切换流程。
PING 命令的回复有两种情况。
- 有效回复:返回 +PONG、-LOADING 和-MASTERDOWN 中的任何一种。
- 无效回复:有效回复之外的回复,或者不在指定时间内返回任何回复。
“ Chaya:“主观下线和客观下线的作用是什么?”
主要是为了避免出现哨兵误判 master 运行的情况,一旦出现误判,就会出现 master 实际没有下线,可是哨兵误以为其已经下线的情况,接着就会启动主从故障切换流程,之后的选主和通知操作都会消耗大量资源。
误判一般会发生在集群网络压力较大、网络拥塞或者是 master 本身压力较大的情况下。
既然一个哨兵容易误判,那就使用多个哨兵进行投票判断。哨兵机制也是类似的,采用多实例组成的集群模式进行部署,就是哨兵集群。
引入多个哨兵实例一起进行判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。
同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,也能降低误判率。
主观下线
主观下线(Subjectively Down,SDOWN)指一个哨兵认为一个 Redis 实例已经不可用或者已经下线,这有可能是网络不通、心跳超时或连接失败等原因导致的。
例如对于 master 或者 slave,在 down-after-milliseconds 指定的毫秒数之内,如果没有向哨兵发送的 PING 命令回复,或者返回一个错误,那么哨兵会将这个服务器标记为主观下线。
需要注意的是,Redis 的哨兵的主要目标是确保 master 的高可用性,而不是 slave 的高可用性。
因此,主观下线和客观下线的主要关注点通常是 master。slave 通常不会被单独标记为客观下线,因为它们不承担 master 的关键角色,它们的主要责任是复制数据。
客观下线
判断 master 是否下线不能只由一个哨兵说了算,只有过半的哨兵判断 master 主观下线,才能将 master 标记为客观下线,如图 3-19 所示。
3-19
之前提到过 sentinel monitor <master-name> <ip> <redis-port> <quorum> 的配置,参数 quorum 是判断客观下线的依据之一,意思是至少有 quorum 个哨兵判定这个 master 主观下线,才会将这个 master 标记为客观下线。
只有 master 被判定为客观下线,才会进一步触发哨兵执行主从切换流程。
- 自动故障切换
“ Chaya:“一旦判断 master 客观下线,就在 slave 中选一个作为新的 master 吗?”
哨兵的第二个任务是选择一个 slave 作为新的 master,并对外提供服务。之后其他 slave 会与新的 master 进行主从复制,这个过程叫作自动故障切换,如图 3-20 所示。
3-20
吃瓜群众:“如何从众多 slave 中选出一个做 master 呢?”
Chaya:“我觉得筛选过程就像找恋爱对象,每个人心中都有标尺,会通过直觉、习惯和自己的标准从所有的追求者中选择一个最适合自己的。”
类似地,Redis 有自己的筛选规则,按照一定的筛选条件和打分策略,选出一个“节点”担任 master。
筛选条件
“ Chaya:“有哪些筛选条件?”
- 下线或网络断连的 slave 直接丢弃。
- 网络无异常:slave 最后一次响应 PING 命令的时间不能超过 5 倍 PING 周期;slave INFO(每 10s 发送一次 INFO 命令)的信息更新时间不能超过 3 倍 INFO 刷新周期。
- 评估过往的网络状态:slave 与 master 断开连接,断连时间不能超过(现在-master 被标记为下线的时间)+(master 的 down-after- milliseconds 配置项的值乘以 10),单位是毫秒。
总之,下线或者网络经常断开的 slave 不能要。如果新的 master 很快出现网络故障,就又得重新选择新的 master,这不“闹着玩”吗,得排除掉!
打分
过滤掉不合适的 slave 之后,使用快速排序对 slave 列表进行打分,按照以下排序找出“王者”。
- slave 优先级:通过 replica-priority 100 配置项,给不同的 slave 配置不同优先级,默认是 100,值越低,优先级越高,配置为特殊值 0 表示不会晋升为 master。
- 更大复制偏移量(processed replication offset):已复制的数据量越多,slave_repl_offset 与 master_repl_offset 的差值就越小。
- slave runID:在优先级和复制进度都相同的情况下,runID 最小的 slave 得分最高,该 slave 会被选为新的 master。
哨兵向筛选出来的 slave 发送 slave no one 命令,使得该 slave 成为新的 master,哨兵并不关心命令返回的结果,它会发送 info 命令给 slave,并根据命令的回复内容确认 slave 是否成功转换为 master。
“ Chaya:“旧的 master 重新恢复正常时要怎么处理?”
旧的不去,新的不来,有些人一旦错过就不在,既然已经错过,相逢也只能是过客。
原 master 恢复正常,重新连接哨兵,这时集群已经有新的 master,所以旧的 master 被哨兵降级为 slave。
- 通知
新的 master 出现后,哨兵还有一件重要的事情要做——将新的 master 的连接信息通过 slave 命令发送给其他 slave,通知 slave 执行 replacaof 命令和新的 master 建立连接进行主从复制。
接着,哨兵会定时给 slave 发 INFO 命令,从 INFO 命令的回复内容来确认 slave 是否与新的 master 成功建立连接。检测到所有 slave 都与新的 master 建立连接,自动故障切换就完成了。
如果还有剩余 slave 没有连上新的 master,则哨兵还会再做一次努力,再次向这些 slave 发送 slave 命令,要求他们与新的 master 建立连接。
- 配置提供者
Redis 客户端只需要跟哨兵打交道,就可以无感知地连接到新的 master,最重要的原因是哨兵提供了一些 API 来检查主从节点的运行状况。
哨兵集群实现原理
如果只有一个哨兵就会存在单点故障问题。Redis sentinel 是一个分布式系统,由多个哨兵协作组成集群实现高可用。
- 当多个哨兵达成一致认为某个 master 不可用时,才执行故障迁移,降低了误报的概率。
- 不需要所有哨兵都可用,哨兵集群依然可以正常工作。
“ Chaya:“哨兵是如何感知其他哨兵节点的呢?又如何知道 slave 节点的信息并监控呢?当 master 不可用时,到底由哪个哨兵来执行自动故障切换呢?”
- 发布/订阅机制
哨兵互相发现
哨兵之间可以互相感知发现,这归功于 Redis 的发布/订阅机制。
当哨兵与 master 建立连接后,使用发布/订阅机制在特殊的频道发布自己的信息,例如 IP 地址和端口,同时订阅该频道获取其他哨兵发布的消息。
master 有一个 sentinel :hello 的专用通道,用于哨兵之间发布和订阅消息。
可以比喻为哨兵利用 master 建立的sentinel :hello 微信群发布自己的消息,同时关注其他哨兵发布的消息,如图 3-21 所示。
3-21
哨兵如何感知并监控 slave
哨兵之间建立连接形成集群还不够,哨兵还需要跟所有 slave 建立连接,否则无法监控它们。除此之外,如果发生了主从切换也需要通知 slave 重新与新的 master 建立连接进行数据同步。
哨兵向 master 发送 INFO 命令,master 接收到命令后,将 slave 列表告诉哨兵。
哨兵根据 master 响应的 slave 名单信息与所有 salve 建立连接,并且根据这个连接持续监控 slave,剩下的哨兵也基于此实现监控,如图 3-22 示。
- 选择哨兵执行主从切换
“ Chaya:“master 不可用后,如何选择一个哨兵来执行自动故障切换呢?”
任何哨兵判断 master 主观下线后,都会向其他哨兵发送 is-master-down-by- addr 命令,其他哨兵收到命令后则根据自己与 master 之间的连接状况分别响应 Y 或者 N,Y 表示赞成,N 表示反对。
如果某个哨兵获得了大多数哨兵的赞成票,就标记 master 为客观下线。
例如,一共有 3 个哨兵组成集群,那么 quorum 就可以配置为 2,当一个哨兵获得了 2 张赞成票(包含自己的 1 票)时,就可以标记 master“客观下线”。
获得多数赞成票的哨兵向其他哨兵发送 SENTINEL is-master-down-by-addr <masterip> <masterport> <sentinel.current_epoch>命令,声明自己想要执行主从切换并开始拉票。
其他哨兵则进行投票,投票过程叫作 leader 选举,选举的过程借鉴了分布式系统中的 Raft 协议。
简单地说,哨兵标记当前 master 客观下线后,通过投票的方式从哨兵集群中选举出一个哨兵作为 leader 角色执行故障切换。
“ Chaya:“我发现判断 master 是否客观下线和哨兵拉票选举 leader 是同样的命令。”
没错,is-master-down-by-addr 命令有两个作用:
- 一是询问其他哨兵是否认为某个 master 已经主观下线;
- 二是开始进行自动故障切换时,当前哨兵向其他哨兵实例进行"拉票",让其他哨兵选举自己为 leader。
哨兵想要成为 leader 没那么简单,得有两把“刷子”。需要满足以下条件。
- 获得其他哨兵过半的投票。
- 投票的数量大于或等于 quorum 的值。
如果 sentine 集群有 2 个实例,此时,一个哨兵要想成为 leader,那么必须获得 2 票,而不是 1 票。
所以,如果有一个哨兵宕机了,那么此时的集群是无法进行主从库切换的。因此,通常我们至少会配置 3 个哨兵实例。
- 发布/订阅机制
在 Redis 中,发布/订阅(Pub/Sub)机制发布不同事件,让客户端订阅消息。哨兵提供的消息订阅频道有很多,不同频道包含了主从库切换过程中的不同关键事件。
master 相关
与 master 相关的消息订阅频道。
◎ +sdown:节点处于主观下线状态。
◎ -sdown:节点不再处于主观下线状态。
◎ +odown:节点进入客观下线状态。
◎ -odown:节点退出客观下线状态。
◎ +switch-master:master 地址发生了变化。
slave 相关
与 slave 相关的消息订阅频道。
◎ +slave-reconf-sent:leader 哨兵发送 REPLICAOF 命令重新配置从库。
◎ +slave-reconf-inprog:slave 配置了新的 master,但是尚未同步。
◎ +slave-reconf-done:slave 配置了新的 master,并完成了数据同步。
Redis 的发布/订阅机制尤其重要,有了发布/订阅机制,哨兵和哨兵之间、哨兵和 slave 之间、哨兵和客户端之间就都能建立起连接了,各种事件的发布也是通过这个机制实现的。
最后欢迎 加入苏三的星球 ,你将获得: 商城微服务实战、秒杀系统实战 、 商城系统实战、秒杀系统实战、代码生成工具、系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题、面试八股文等多个优质专栏。
还有1V1答疑、修改简历、职业规划、送书活动、技术交流。
目前星球已经更新了4900+篇优质内容,还在持续爆肝中.....
