前言
相关系列
- 《Redis & 目录》
- 《Redis & 哨兵 & 源码》
- 《Redis & 哨兵 & 总结》
- 《Redis & 哨兵 & 问题》
参考文献
- 《Redis的主从复制和哨兵机制详解》
- 《Redis中的哨兵(Sentinel)》
- 《【Redis实现系列】Sentinel自动故障转移》
概述
简介
哨兵机制实现了对主从同步机制故障转移的自动化。所谓故障转移是指在主机宕机时将从机充当主机以对外服务的过程,该过程的行为包括但不限于修改从机的配置文件/重启从机/重新建立主从关系/修改客户端的配置文件/重启客户端等,是一个人为执行相当繁琐/耗时且极易出错的操作,因此Redis便提供了哨兵机制来优化这一点。哨兵提供了对目标主机(及其从机)的实时监测服务,并支持在主机宕机时自动筛选可用度最高的从机来代替其工作,从而得以在全然无人干预的情况下自动完成上述故障转移的所有行为。
哨兵会对外提供Redis的连接地址。在哨兵已开启的情况下,客户端将不再于配置文件中硬编码Redis的连接地址,而是会转而从哨兵中动态获取。这么做的好处在于客户端无需在于主机宕机时修改配置文件并重启,因为哨兵会直接对其提供新主机的地址,从而得以在无任何人为操作的情况下完成对新主机的自动连接。这里需要注意的是:客户端与Redis的连接方式依然是直接连接,而非以哨兵为中间件的间接连接。这是因为转接会对Redis的内存读/写造成较大的性能影响,而先从哨兵处获取地址再直接连接便可以在保证内存读/写性能的前提下无缝添加故障转移功能。
哨兵推荐以奇数集群的形式进行工作。哨兵推荐以集群形式工作一方面是为了提升哨兵系统的健壮性/可用性,但更多是为了避免单哨兵对主机运行状态的错误判断。即通过对多哨兵监测结果的综合判断来提升判定的准确性/可行度,从而避免因为网络波动等原因而错误判定主机已宕机的情况发生。那为什么最好是奇数(推荐3/5)集群呢?这是因为哨兵会在主机宕机时通过投票来选举可用度(监测性)最高(好)的从机(哨兵)来充当(筛选)新主机,而奇数集群将有效避免平票的情况发生,从而进一步避免重复选举而造成的性能损失。
监测相同主机的多个哨兵默认同属一个集群。哨兵集群无需刻意搭建,即哨兵集群的搭建无需通过在配置文件中添加/修改配置项来达成。这是因为Redis对于此处设计采用了轻耦合的全自动化方案,即一旦有多个哨兵监测了相同主机,那么这些哨兵就会自动基于该主机进行通讯并建立/加入集群。
使用
启动
哨兵启动分为“配置/指令”两步。所谓配置是指在sentinel.conf文件中添加哨兵启动的必要条件,其具体分为“主机的地址信息”及“故障转移时允许主机客观下线/哨兵成为领导的哨兵数量”。
名称:sentinel monitor <master-name> <ip> <redis-port> <quorum>
作用:设置哨兵监测的主机集,以及故障转移时允许主机客观下线/哨兵成为领导的哨兵数量。
---- 名称:master-name @ 主机名称
---- 作用:主机的自定义名称,只允许英文/数字/下划线。
---- 默认:mymaster
---- 名称:ip @ IP
---- 作用:主机的IP。
---- 默认:127.0.0.1
---- 名称:redis-port @ Redis端口
---- 作用:主机的端口。
---- 默认:6379
---- 名称:quorum @ 法定人数
---- 作用:故障转移时允许主机客观下线/哨兵成为领导的哨兵数量。该参数通常为集群总数的一半加1,即3/5个哨兵则为2/3,从而保证下线判断/投票选举的准确性。此外集群中所有哨兵对该参数的配置应该统一,否则会增大集群选举出多个领导哨兵的可能,而这种处理是需要额外开销的。
---- 默认:2
完成对配置文件的修改后,可通过{REDIS-SEVER/REDIS-SENTINEL}指令启动哨兵,但通常使用{REDIS-SENTINEL}指令会更多一些。
- REDIS-SENTINEL :启动哨兵以监测指定主机。
------------------------- 入参 -------------------------
config-file-path @ 配置文件路径:sentinel.conf文件地址。 - REDIS-SEVER --SENTINEL:启动哨兵以监测指定主机。
------------------------- 入参 -------------------------
config-file-path @ 配置文件路径:sentinel.conf文件地址。 - INFO SENTINEL:查询哨兵信息。
多监测
哨兵支持多监测。哨兵支持同时监测多台主机,只需在配置文件中添加多台监测主机即可,因此一台哨兵会基于不同的主机而建立/加入多个集群…即使这多个集群的成员可能完全一致。多监测是一项非常实用的功能,因为它让你在面对新主机时无需再全量部署一套新的哨兵集群,这对于采用级联架构的主从同步来说无疑是超重磅的好消息,因为在物理层面上为所有主从机(携带有从机的从机)都建立独立的哨兵集群对资源/人工/维护来说都是极大的挑战…多监测配置如下:
# ---- 同时检测三台主机。
sentinel monitor mymaster1 127.0.0.1 6379 2
sentinel monitor mymaster2 127.0.0.1 6479 3
sentinel monitor mymaster3 127.0.0.1 6579 3
通讯
集群中的哨兵存在“直接/间接”两种通讯方式。所谓直接通讯是指基于哨兵间的TCP连接进行的同步通讯,而所谓间接通讯则是指基于Redis/主机提供的发布/订阅机制进行广播式的异步通讯。当哨兵成功启动时,其会自动在主机上订阅名为__sentinel__:hello的特殊频道,该频道被专用于为监测自身的哨兵提供通讯桥梁。而当订阅完成后,哨兵会向__sentinel__:hello频道发布包含自身地址在内的各项信息。这些信息会被同样监测/订阅该主机/频道的其它所有哨兵接收并解析,从而得以基于信息中的地址来与新哨兵建立TCP连接,因此哨兵间的直接通讯是基于先天存在的间接通讯而后天建立的。
哨兵通过间接通讯在集群中进行无/异步回应通讯,其主要传输的信息/作用具体如下:
- 自身的地址信息:哨兵会发布自身的地址信息,从而令集群中的其它哨兵与自身建立直接通讯;
- 主/从机的监测结果:哨兵会发布自身对机的监测结果,用于其它哨兵汇总/调整自身的监测结果以提升准确性。
哨兵通过直接通讯在集群中进行同步回应通讯,这也是哨兵在进行故障转移时的核心通讯方式,其主要传输的信息/作用具体如下:
- 心跳:哨兵会持续发送心跳来监测集群中其他哨兵的运行状态,如果未能在规定时间内收到响应,那么它会认为该哨兵可能已经宕机;
- 故障转移信息:哨兵会在主机宕机时与其他哨兵进行数据交互来保证故障转移的正常执行,这包含但不限于询问/投票/选举/通知等。
直接通讯是故障转移的通讯任务承担者。由于间接通讯服务由主机负责提供,故而当哨兵监测到主机宕机而触发故障转移时其理论上是不用的,因此哨兵间的直接通讯才是故障转移的通讯任务实际承担者。这里之所以强调这一点是因为网上很多文章都认为间接通讯才是哨兵集群在故障转移期间的交互方式,但该结论显然是非常荒谬的,因为一台已宕机的主机并无法提供发布/订阅服务。那既然存在如此明显的逻辑错误,为什么网上文章还会这般介绍呢?这是因为故障转移期间间接通讯也确实是存在的,即哨兵会在监测到机运行状态变化时发布该结果。该操作在通常情况下其实属于非常正常的数据共享行为,但在主机宕机时依然执行就显得莫名其妙。而实际上这是哨兵基于主机可能未宕机而采取的乐观方案,因为该主机宕机可能只是哨兵因为网络等原因而导致的误判,因此即使在该情况下哨兵也依然会继续向集群中的其它哨兵发布监测结果。
故障转移
心跳/下线/询问
- PING:向Redis/哨兵发送心跳以监测运行状态。
// ---- 向Redis/哨兵发送心跳。
> PING
// ---- +PONG回应表示Redis/哨兵已接收到PING命令,并且连接正常,服务处于健康状态;
+PONG
// ---- -LOADING回应表示Redis/哨兵已接收到PING命令,但服务本身正在执行数据加载,例如
// 加载RDB/AOF文件等,暂无法提供具体服务。
-LOADING
// ---- 在Redis复制环境中,如果从机检测到与主机的连接已经丢失,并且无法通过哨兵或其他
// 机制重新建立连接,那它可能会以某种方式报告主机不可用的状态(注意,这不是PING命令的直
// 接回应,而是可能通过其他命令(如INFO replication)或哨兵系统的通知来获取的,但可能与
// PING命令的上下文相关)。
-MASTERDOWN
// ---- 在极少数情况下,如果Redis/哨兵遇到内部错误或配置问题,它可能会返回其他类型的错
// 误消息。这些错误消息的具体内容和含义取决于具体的错误情况,可能包括资源不足、配置错误、命
// 令语法错误等。
无回复/其它无效回复
哨兵对机宕机的判定分为“主观/客观”两级。哨兵会以1秒(无法手动调整)为周期向机持续发送{PING}指令以监测其运行状态,并在{sentinel down-after-milliseconds}配置项的{milliseconds}参数时间内未接到有效(+PONG/-LOADING)回复的情况下主观认定机已经下线。但由于该结论可能只是网络延迟而导致的误判,因此哨兵会在主观下线的基础上继续综合其它哨兵的监测结果来提升判定的准确性,并在符合条件的情况下再将主机(从机不参与客观下线判定)判定为代表实际宕机的客观下线…即使其并未真的宕机:
- S_DOWN @ Subjectively Down @ 主观下线:表示机可能(小概率)宕机,但也可能是网络延迟等原因而导致的哨兵误判;
- O_DOWN @ Objectively Down @ 客观下线:表示主机实际(大概率)宕机,这是哨兵在主机已主观下线的基础上综合集群中其它哨兵的监测结果而得出的结论。这种情况下即使主机没有宕机也会被视作宕机看待。
哨兵会向其它哨兵发起询问。为了确定已主观下线的主机是否真的宕机,哨兵会向集群中的其它哨兵发起询问,以确定其它哨兵是否也已将主机判定为(主观/客观)下线。由于此时间接通讯已不可靠,因此该询问会通过直接向其它哨兵发送{SENTINEL is-master-down-by-addr}指令的方式来进行。接收到指令的其它哨兵会根据其携带的主机IP/主机端口去检查/返回主机的运行状态,而如果哨兵在统计回应时发现判定主机已下线的其它哨兵总数已达{sentinel monitor}配置项的{quorum}参数数量,那么便会将主机判定为客观下线。
- SENTINEL is-master-down-by-addr <current_epoch> :用于询问目标哨兵对指定主机运行状态的判定结果,以及向目标哨兵发起投票请求。
------------------------- 入参 -------------------------
ip @ IP:主机IP;
port @ 端口:主机端口;
current_epoch @ 当前纪元:当前哨兵的当前纪元,即哨兵集群发生状态变化的近似次数,可变相代表当前哨兵的监测性强弱/持有监测数据的新旧;
runid @ 运行ID:当前哨兵的运行ID,用于标志当前哨兵,并在选举时作为投票的目标使用。如果指令调用是为了进行询问,那么该变量值为“”。
------------------------- 出参 -------------------------
down_state @ 下线状态:表示主机是否下线,0表示未下线;而1表示已下线;
leader_runid @ 领导运行ID:目标哨兵已投票哨兵的运行ID,当前可以此判断目标哨兵已投票哨兵是否为自身,如果没有则为“”或空;
leader_epoch @ 领导纪元:目标哨兵已投票哨兵的当前纪元,当前可以此判断目标哨兵已投票哨兵是否为自身,如果没有则为“*”或空。
注意!主观/客观下线都只基于单个哨兵而言,即使客观下线是多哨兵的综合判定结果也是如此。因此某哨兵将主机判定为客观下线并不代表整个集群也都如此认为,即集群中的其它哨兵对主机状态的判定可能依然只是主观下线/正常运行而已…当然在正常运行的情况下,这部分哨兵理论上也会随着监测的持续进行而陆续将主机判定为客观下线。
名称:sentinel down-after-milliseconds <master-name> <milliseconds>
作用:设置哨兵监测的主机,以及故障转移时允许主机客观下线/哨兵成为领导的哨兵数量。
---- 名称:master-name @ 主机名称
---- 作用:主机的自定义名称,只允许英文/数字/下划线。
---- 默认:mymaster
---- 名称:milliseconds @ 毫秒
---- 作用:主机主观下线的无回应毫秒时间。
---- 默认:30000
投票/选举/当前纪元
判定主机客观下线的哨兵会发起投票。所谓投票是指哨兵向其它哨兵请求投票以将自身选举为领导哨兵(负责执行故障转移)的过程,由于在通常情况下集群中的哨兵会在极短的时间差内陆续将主机判定为客观下线,因此投票一般都会出现并发/竞争的情况,因为每个哨兵在一轮选举中只能投票一次。哨兵请求投票与发起询问使用的都是{SENTINEL is-master-down-by-addr}指令,区别在于请求投票时哨兵会额外带上标记自身的运行ID以供投票使用。接收到指令的哨兵整体会按“先到先得”的规则进行投票,即如果某哨兵在某轮选举中未曾向任何其它哨兵投票,那么它就会将票投递给首个请求投票的其它哨兵,并拒绝后续其它哨兵的投票请求。而一旦某哨兵获得的票数已达{sentinel monitor}配置项的{quorum}参数数量,那么该哨兵就会自动成为领导哨兵。
Current Epoch @ 当前纪元同样是投票规则的决定性因素。除了“先到先得”以外,当前纪元同样会投票过程造成巨大影响。所谓当前纪元是指哨兵内部用于记录哨兵集群版本的整型变量,而所谓哨兵集群版本则是指哨兵集群发生状态变化/重大事件的次数。由于哨兵会在每次监测到集群发生状态变化/重大事件时递增当前纪元,并且哨兵可能因为网络/宕机等原因而遗漏重大事件,以及机的变化(例如主机宕机)也会导致哨兵集群发生客观下线等重大事件,因此当前纪元大小便可同时反映“哨兵集群的变动多少/单个哨兵的监测性强弱/单个哨兵监测数据的新旧”等多项含义。而由于监测性较强/监测数据较新的哨兵理论上可以更好挑选出可用性较强的从机来作为新主机,因此哨兵在基于“先到先得”规则前进行投票前会先判断请求哨兵的当前纪元是否 >= 自身当前纪元,否则意味着请求哨兵的监测性较弱/监测数据较旧,这种情况下即使其第一个请求也不会获得选票…导致当前纪元递增的状态变化/重大事件包含但不限于以下所示:
- 客观下线:哨兵判定主机客观下线时会递增自身当前纪元;
- 选举投票:哨兵在发起投票时会递增自身当前纪元以标识这一轮选举的开始;
- 配置更新:哨兵配置发生变化(例如在故障转移时变更检测的主机等)时会递增自身当前纪元以反映这些变化。
- 集群变动:哨兵在发现集群中有新哨兵加入/旧哨兵退出时会递增自身当前纪元以记录这种变化。
集群中的哨兵会共享当前纪元以达成统一。哨兵在运行时会持续通过直接/间接通讯对外共享信息,这其中也会包含自身的当前纪元。而当其它哨兵收到这些信息时如果发现自身当前纪元小于其当前纪元,那么会判定自身监测数据较旧而与共享数据进行整合,并会增长(不是递增)当前纪元以达成一致。
选举可能会多次执行。由于网络的不确定性等原因,哨兵集群无法保证必然选举出领导哨兵,即无法保证不出现少票/平票的情况。而在这种情况下上哨兵就会再次投票以开启新的选举,因此选举可能会因为多次执行而影响效率。而这也是哨兵推荐奇数集群的原因,因为这可以有效避免少票/平票的情况发生。
注意!上述内容并非投票选举的完整流程!投票选举的完整流程非常复杂且影响因素众多,故而上文只是摘选了最重要的两点进行阐述!
挑选/通知
领导哨兵会挑选可用度较高的从机作为新主机。领导哨兵会根据“从机的优先级高低/主机数据的同步进度/从机运行ID的大小”等因素挑选出自认为可用性最高的从机,并发送{SLAVEOF/REPLICAOF}指令设置为新主机/解除主从关系。随后领导哨兵会向其它哨兵发送{未知}/从机发送{SLAVEOF/REPLICAOF}指令以令之修改自身“内存/文件”配置,从而实现对新主机的状态监测/数据同步。此外领导哨兵还会持续监测旧主机,一旦其发现旧主机恢复运行,那么按照默认配置领导哨兵就会发送{SLAVEOF/REPLICAOF}指令将之设置为新主机的从机,从而实现其对新主机的数量同步。
- SLAVEOF/REPLICAOF :Redis在5.0.0版本前/后用于建立/取消主从关系的指令。
------------------------- 入参 -------------------------
master-ip @ 主机ID:主机的ID。解除主从关系时传NO;
master-port @ 主机端口:主机的端口。解除关系时传ONE。
领导哨兵会将新主机的地址告知客户端。在完成上述的一切行为后,领导会将新主机的地址发送至客户端以重新建立其与Redis服务的直接连接,从而结束故障转移的所有流程。