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
最小的从节点。