什么是Redis-Sentinel集群?
作者:程序员马丁
在线博客:https://open8gu.com
大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。
回答话术
针对普通主从模式无法自动进行故障转移的问题,Redis 在 2.6 及以上版本提供了哨兵功能。
哨兵是一种运行在特殊模式下的 Redis 节点,它用于监控集群中的节点监控状态,并在主节点下线后实现自动故障转移。此外,为了避免单点故障问题,哨兵通常也会集群部署。
当启动一台哨兵时,它会连接到主节点,并向该主节点发送 INFO
命令获取从节点相关信息,并连接到从节点。在此后,哨兵将会定期向它监控所有节点发送 INFO
,从而及时了解集群节点的变动情况。
此外,每个哨兵连接到主节点后,都会订阅主节点的 __sentinel__:hello
频道,并且每隔 2s 向该频道发送 hello 消息,该消息包含了自己的地址和端口信息。其他订阅了该频道的哨兵节点收到消息后,将会并确认对方的存在,并与其建立链接。
每个哨兵节点都会定期向主从节点以及其他哨兵节点发送 ping 请求。当有节点超时未响应时,它将认为该节点主观下线。如果这个节点恰好是主节点,那么它们还会向其他哨兵节点确认对方是否也认为该节点主观下线,当有指定数量的哨兵认为该节点主观下线后,哨兵会认为该节点客观下线,并进行故障转移。
在故障转移开始前,哨兵集群会选择出一个领头哨兵负责整个过程,领头哨兵将会依次根据从节点在配置文件中配置的 slave-priority
优先级、节点的数据复制进度和 runID 选择出优先级最高的从节点作为新的主节点,并将其他从节点指定为它的从节点,以完成故障转移。
问题详解
1. 哨兵模式
Redis 默认提供的主从模式不具备故障转移的能力,当主节点宕机后,需要用户手动的进行主从切换,在这段时间内整个集群实际上处于不可用的状态。为了解决这个问题,Redis 在 2.6 及以上版本提供了哨兵模式。
简单的来说,哨兵模式与普通的主从复制相比,新增了一类哨兵节点,它是一个特殊的 Redis 进程,哨兵节点本身不对外提供任何读写服务,它的作用是:
- 监控与通知:与所有节点保持心跳,监控它们的监控状况。
- 自动故障转移:当主节点故障后,自动选举新的主节点并完成主从切换,实现自动故障转移。
- 通知告警:用户可以通过哨兵节点的 API 订阅消息,从而在节点状态异常时获得通知,又或者在完成主从切换后得知新的主节点地址。
为了避免哨兵节点单点故障,因此哨兵也需要集群部署。多个哨兵节点可以组成一个哨兵集群,集群中的哨兵节点除了监控主从集群中节点的健康状况外,还会监控其他哨兵节点的健康状态。
2. 哨兵集群的搭建
当你手头已经有一个主从集群后,你可以启动哨兵节点去监控这个集群。
整个流程大概如下:
- 连接到主节点:哨兵节点根据配置文件,连接到指定的主节点;
- 订阅频道:连接成功后,哨兵节点将会订阅主节点
__sentinel__:hello
的频道。每个节点都会以 2s 为间隔向该频道发送自己的端口和 IP 等信息。此时,若有其他哨兵节点也订阅了这个该频道(换而言之,就是也监控了这个主节点),则它们会通过收到的消息感知到新哨兵的加入,并与新哨兵建立连接; - 获取节点信息:当订阅频道后,哨兵节点将会每隔 10 秒向所有节点发送
INFO
命令获取相关信息,比如主节点的从节点信息,节点的 IP 、端口号、 runID 和 offset 等。
3. 故障检测
当哨兵节点启动以后,每隔 1 秒,它将会向集群中的主从节点与其他的哨兵节点发送 ping 请求,以确认对方的健康状况。如果在指定的超时时间 down-after-millisenconds
内没有收到响应,那么它就会认为没有响应的那个节点已经掉线,将其标记为 sdown
。这种这种下线状态称为“主观下线”。
如果下线的是从节点或者其他哨兵节点,那么直到其重新上线为止,哨兵都会认为其已经不可用。如果下线的是主节点,那么哨兵为了防止因为网络波动导致的误判,会向其他的哨兵节点发送 sentinel is-master-down-by-addr
请求,确认对方是否也认定该节点下线。当整个哨兵集群中有足够数量的哨兵(该值通过配置文件中的 quorum
进行配置)节点确认主节点主观下线,那么主节点就会被认定为“客观下线”,并标记为 odown
。
4. 故障转移
4.1. 领头哨兵的选举
在一切开始前,哨兵集群会从所有的哨兵节点中挑选一个领头哨兵,领头哨兵将用来代表其他节点完成重新选主和主从切换的任务。
Redis 基于 Raft 分布式共识算法来实现领头哨兵的选举,这个过程大致如下:
- 确认主节点客观下线的哨兵将成为候选者,它先投自己一票,然后邀请其他哨兵向自己投票;
- 其他哨兵如果尚未投票,则会将把赞同票投给邀请它投票的候选哨兵,否则就不投票;
- 所有的候选哨兵都统计自己的得票数,当得票数满足下述两条件时,该节点当选为领头哨兵:
- 当得票数超过哨兵集群中节点数量的一半,即
n/2+1
; - 得票数且超过了认定节点客观下线所要求的哨兵数量(即配置文件中的
quorum
配置);
- 当得票数超过哨兵集群中节点数量的一半,即
- 如果在规定时间内或投票结束后都没有选出领头哨兵,则再进行一轮投票,直到选出领头哨兵为止。
由于选举机制需要保证有一个节点可以拿到超半数票,因此为了保证哨兵集群的高可用,一个最小的哨兵集群至少需要有三个节点,这样当任意一个节点下线后,哨兵集群仍然可以正常选举出领头哨兵去做故障转移。
此外,我们可以注意到,由于故障检测只受 quorum
影响,而不受哨兵数量的影响。因此,当 quorum
被设置了一个过小的值 —— 比如为低于 n/2+1
——的时候,可能会出现虽然哨兵确认节点客观下线了,但是却由于在线的哨兵数量不足以选举出领头哨兵,最终无法进行故障转移尴尬局面。
比如,我们有一个由五个节点组成的哨兵集群,当我们把 quorum
设置为 2 时,如果挂掉了三个哨兵节点,此时哨兵集群可以确认主节点已经客观下线,但是由于哨兵节点数量不足,无法选出领头哨兵,所以即使发现了问题也无法完成故障转移。
4.2. 主从切换
选举出领头哨兵后,它将从被自己监控的所有从节点中,先排除下述节点:
- 排除所有已经掉线的从节点;
- 排除五秒内没有响应哨兵的
INFO
命令的从节点(当出现主节点客观下线后,哨兵对节点的INFO
命令已经变为一秒请求一次)。 - 排除与主节点断连超过
down-after-milliseconds * 10
毫秒的从节点(down-after-milliseconds
即为主节点的超时时间)。
然后剩下的节点都有资格参与选举,此时哨兵再根据以下条件选择出优先级最高的节点作为新的主节点:
- 优先级配置:先尝试根据
slave-priority
配置,选出一个优先级最高的节点; - 数据同步进度:如果所有节点优先级配置均相同,则从中挑选出一个复制进度最高的(即主从复制中提到的从节点的 offset);
- 启动时间:如果所有的节点复制进度均相同,则挑选一个 runID 最小的节点(即最新的从节点)。
当选出一个新的主节点后,领头哨兵将会发送 slave no one
命令将该节点真正的升级为主节点,然后向其他的从节点发送 slaveof
命令让它们成为这个新主节点的从节点,至此,故障转移的就完成了。
需要注意的是,在这个过程中,由于主从复制延迟,当主从切换后,旧的主节点尚未同步从节点的数据可能就会丢失。
关于从节点的
runID
与offset
,请参见:✅ Redis 主从复制的原理是什么?
5. 如何防止脑裂?
在正常情况下,当发生主从切换时,客户端将会从哨兵收到主节点切换的通知,然后与旧的主节点断开连接,并连接到新的主节点。不过,如果有的客户端与哨兵也断开了连接,那么它将无法意识到主节点已经切换,并且还是连接到旧的主节点。此时,整个 Redis 集群实际上同时有两个主节点在工作,这就是脑裂问题。
当出现脑裂问题时,由于有的客户端连接的是已下线的主节点,而有的客户端连接到的是新的主节点,因此两个节点的数据将会不一致。当已下线的主节点重新连接到集群后,如果将该节点降级为从节点,那么在进行数据同步时,从它下线以后到现在重新上线这段时间的数据就会全部丢失。
为此,我们可以通过调整两个参数防止这种情况出现:
min-replicas-to-write
:主节点最少要有多少个可用的从节点。min-replicas-max-lag
:如果主节点要允许客户端访问,那么它和它的从节点最大的复制延迟时间。
简单的来说,这两个参数可以用于规定,如果主节点如果要对客户端提供服务,那它至少得有几个可用的从节点,并且它和它的从节点之间的复制延迟不大于多少秒。
如此一来,当主节点掉线时,我们就可以让客户端能够及时发现问题,然后重新向哨兵确认当前的主节点。在最大限度的避免的数据不一致问题后,当旧的主节点节点重新上线,就可以放心的让它去当从节点,而不必担心数据丢失的问题。
不过,有得必有失,为了避免主节点“罢工”,min-replicas-to-write
调的越高,从节点就需要部署的越多,min-replicas-max-lag
调的越小,那主从节点间的网络就要越稳定。因此,实际使用时,还是需要结合自己的业务需求来衡量要采用什么样的配置。