Redis 哨兵是 Redis 的高可用实现方案。本节首先会回顾主从复制模式下故障处理可能产生的问题,而后引出高可用的概念,最后重点分析 Redis 哨兵的基本架构、优势,以及是如何实现高可用的。
本文大部分摘录自:Redis开发与运维
主从复制的问题 链接到标题
Redis 的主从复制模式可以将主节点的数据改变同步给从节点,这样从节点就可以起到两个作用:
- 作为主节点的一个备份,一旦主节点出了故障不可达的情况,从节点可以作为后备“顶”上来,并且保证数据尽量不丢失(主从复制是最终一致性)。
- 从节点可以扩展主节点的读能力,一旦主节点不能支撑住大并发量的读操作,从节点可以在一定程度上帮助主节点分担读压力。
但是主从复制也带来了以下问题:
- 一旦主节点出现故障,需要手动将一个从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要人工干预。
- 主节点的写能力受到单机的限制。
- 主节点的存储能力受到单机的限制。
其中第一个问题就是Redis的高可用问题,也是 Redis 引入哨兵机制的原因。
故障转移 链接到标题
在没有哨兵的情况下,Redis 的主节点发生故障后:
客户端连接主节点失败,两个从节点与主节点连接失败造成复制中断。

如果主节点无法启动,则要选出一个从节点(slave-1),对其执行 slaveof no one 命令使其成为新的主节点。

原来的从节点(slave-1)成为新的主节点后,更新应用方的主节点信息,重新启动应用方。

客户端命令另一个从节点(slave-2)去复制新的主节点(new-master)。

待原来的主节点恢复后,让它去复制新的主节点。

整个过程需要人工介入,且仍然存在以下问题:
- 判断节点不可达的机制是否健全和标准。
- 如果有多个从节点,怎样保证只有一个被晋升为主节点。
- 通知客户端新的主节点机制是否足够健壮。
Redis 哨兵正是用于解决这些问题。
哨兵的自动故障转移 链接到标题
当主节点出现故障时,Redis 哨兵能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。
Redis 哨兵是一个分布式架构,其中包含若干个哨兵节点和 Redis 数据节点,每个哨兵节点会对数据节点和其余哨兵节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他哨兵节点进行“协商”,当大多数哨兵节点都认为主节点不可达时,它们会选举出一个哨兵节点来完成自动故障转移的工作,同时会将这个变化实时通知给 Redis 应用方。整个过程完全是自动的,不需要人工来介入,所以这套方案很有效地解决了 Redis 的高可用问题。

有了哨兵之后,整个故障转移的逻辑就变成:
主节点出现故障,此时两个从节点与主节点失去连接,主从复制失败。

每个哨兵节点通过定期监控发现主节点出现了故障。

多个哨兵节点对主节点的故障达成一致,选举出 sentinel-3 节点作为领导者负责故障转移。

哨兵领导者节点执行了故障转移,整个过程和上节完全一致,只不过是自动完成的。

故障转移后整个 Redis 哨兵的拓扑结构如下图所示:

通过上面介绍的 Redis 哨兵逻辑架构以及故障转移的处理,可以看出:
- 监控:哨兵节点会定期检测 Redis 数据节点、其余哨兵是否可达。
- 通知:哨兵节点会将故障转移的结果通知给应用方。
- 主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系。
- 配置提供者:在 Redis 哨兵结构中,客户端在初始化的时候连接的是哨兵节点集合,从中获取主节点信息。
同时看到,Redis 哨兵包含了若个节点,这样做也带来了两个好处:
- 对于节点的故障判断是由多个哨兵节点共同完成,这样可以有效地防止误判。
- 哨兵节点集合是由若干个哨兵节点组成的,这样即使个别哨兵节点不可用,整个哨兵节点集合依然是健壮的。
哨兵本身就是独立的 Redis 节点,只不过它们有一些特殊,它们不存储数据 (这里指接收用户的写命令,毕竟哨兵节点必须存储 +switch-master 信息),只支持部分命令。
那么,哨兵是如何判断节点故障的呢,这里就涉及到主观下线和客观下线的区别了。
主观下线和客观下线 链接到标题
每个哨兵节点会每隔1秒对主节点、从节点、其他哨兵节点发送 ping 命令做心跳检测,当这些节点超过 down-after-milliseconds 没有进行有效回复,哨兵节点就会对该节点做失败判定,这个行为叫做主观下线。
主观下线是当前哨兵节点的一家之言,可能存在误判。从节点、哨兵节点在主观下线后,没有后续的故障转移操作。
当哨兵主观下线的节点是主节点时,该哨兵节点会通过 sentinel is-master-down-by-addr 命令向其他哨兵节点询问对主节点的判断,当超过
sentinel is-master-down-by-addr 命令的格式为:
sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
其中,
current_epoch:当前配置纪元。runid:此参数有两种类型,不同类型决定了此 API 作用的不同。- 当
runid等于 “*” 时,作用是哨兵节点直接交换对主节点下线的判定。 - 当
runid等于当前哨兵节点的runid时,作用是当前哨兵节点希望目标哨兵节点同意自己成为领导者的请求。
- 当
sentinel is-master-down-by-addr 的返回结果包含三个参数:
down_state:目标哨兵节点对于主节点的下线判断,1是下线,0是在线。leader_runid:当leader_runid等于 “*” 时,代表返回结果是用来做主节点是否不可达,当leader_runid等于具体的runid,代表目标节点同意runid成为领导者。leader_epoch:领导者纪元。
领导哨兵节点选举 链接到标题
主节点的故障转移只需要一个哨兵节点来完成,在此之前,哨兵节点之间会进行领导者选举。Redis 使用 Raft 算法实现领导者选举:
- 每个在线的哨兵节点都有资格成为领导者,当它确认主节点主观下线时候,会向其他哨兵节点发送
sentinel is-master-down-by-addr命令,要求将自己设置为领导者。 - 收到命令的哨兵节点,如果没有同意过其他哨兵节点的
sentinel is-master-down-by-addr命令,将同意该请求,否则拒绝。 - 如果该哨兵节点发现自己的票数已经大于等于
max(quorum, num(sentinels)/2+1),那么它将成为领导者。 - 如果此过程没有选举出领导者,将进入下一次选举。
最后,哪些节点会入哨兵领导者的法眼,成为新的主节点呢:
- 过滤:“不健康”(主观下线、断线)、5秒内没有回复过哨兵节点 ping 响应、与主节点失联超过
down-after-milliseconds*10秒。 - 选择
slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在则继续。 - 选择复制偏移量最大的从节点(复制的最完整),如果存在则返回,不存在则继续。
- 选择
runid最小的从节点。