Redis Sentinel架构

复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的,但它故障恢复无法自动化,因此Redis提供了哨兵(Sentinel)这么一个高可用方案。Redis Sentinel由两部分组成,哨兵节点和数据节点:

  • 哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。
  • 数据节点:主节点和从节点都是数据节点。

Redis Sentinel具有以下功能:

  • 监控:哨兵通过心跳检测会不断地检查主节点和从节点是否运作正常。
  • 自动故障转移:当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
  • 配置提供者:客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
  • 通知:哨兵可以将故障转移的结果发送给客户端。

客户端连接Redis Sentinel

对于Redis Sentinel,它仅仅完成了服务端的高可用,当master挂掉时能选举出一个新的master节点来完成故障转移,但是我们客户端并没有去连接新的master节点,因此我们还要使得客户端也是高可用的。

Jedis客户端对Redis Sentinel提供了很好的支持。我们只需要向Jedis提供sentinel节点集合和masterName,构造JedisSentinelPool对象,然后便可以像使用普通redis连接池一样来使用了:通过pool.getResource()获取连接,执行具体的命令。

1
2
3
4
5
6
7
8
9
10
11
12
public static void testSentinel() throws Exception {
String masterName = "mymaster";
Set<String> sentinels = new HashSet<>();
sentinels.add("192.168.92.128:26379");
sentinels.add("192.168.92.128:26380");
sentinels.add("192.168.92.128:26381");

JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels); //初始化过程做了很多工作
Jedis jedis = pool.getResource();
jedis.set("key1", "value1");
pool.close();
}

在整个过程中,我们的代码不需要显式的指定主节点的地址,就可以连接到主节点。代码中对故障转移也没有任何体现,就可以在sentinel完成故障转移后自动的切换主节点。之所以可以做到这些,是因为客户端完成了以下几个工作:

  • 遍历sentinel节点,找到一个可用的sentinel节点,通过sentinel get-master-addr-by-name命令获取master节点的信息(IP和端口),之后再对master节点执行role命令判断其是否真的为master节点。
  • 客户端和sentinel使用了一个发布订阅模式,客户端订阅sentinel的某一个频道,当master发生变化时,sentinel向这个频道发布一条消息,客户端就可以获取再对新的master进行一个连接。

前面说过sentinel相当于配置提供者,我们得到了sentinel的集合后就可以通过sentinel节点获取到master的地址。这里要注意sentinel只是配置提供者,而不是代理,二者的区别在于如果是配置提供者,客户端在通过sentinel获得master信息后,会直接建立到master的连接,后续的请求会直接发向master,而如果是代理,客户端的每一次请求都会发向sentinel,sentinel再通过主节点处理请求。

基本原理

三个定时任务

  • 10秒每个sentinel对master和slave执行info,以此发现slave节点并确认主从关系。
  • 2秒每个sentinel通过master节点的channel交换信息(因此sentinel节点之间能够自动感知)。
  • 每1秒每个sentinel对其它sentinel和redis执行ping也就是心跳检测。

主观下线和客观下线

  • 主观下线:在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线。
  • 客观下线:哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr命令询问其他哨兵节点该主节点的状态。如果判断主节点下线的哨兵数量达到设置的法定人数(quorum),则对该主节点进行客观下线。

需要特别注意的是,客观下线是主节点才有的概念,如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作。

领导者选举

由于只需要一个sentinel节点完成故障转移,因此sentinel内部需要选举出一个节点作为领导者,同样也是通过sentinel is-master-down-by-addr这个命令完成领导者的选举(这正是这条命令的第二个作用)。过程如下:

  1. 每个做主观下线的sentinel节点向其它sentinel节点发送命令,要求将它设置为领导者。
  2. 收到命令的sentinel节点如果没有同意通过其它sentinel节点发送的命令,那么将同意该请求,否则拒绝。
  3. 如果该sentinel节点发现自己的票数已经超过sentinel集合半数并且超过quorum,那么将它成为领导者。

故障转移

完成了领导者选举后,由领导者进行故障转移操作:

  1. 从slave节点中选出一个“合适的”节点作为新的master节点:选择优先级最高的从节点(由slave-priority指定);如果优先级无法区分,则选择复制偏移量最大的从节点;如果仍无法区分,则选择runid最小的从节点。
  2. 对上面slave节点执行slaveof no one命令让其成为master节点。
  3. 向剩余的slave节点发送命令,让它们成为新master节点的slave节点,复制规则和parallel-syncs参数有关。
  4. 更新原来master节点配置为slave,并保持着对其“关注”,当其恢复后命令它去复制新的master节点。

常见问题

Redis Sentinel无法对从节点进行自动故障转移,在读写分离场景下,从节点故障会导致读服务不可用,需要我们对从节点做额外的监控、切换操作。除此之外,它的存储能力受到单机限制的问题,因此如果我们对扩容有需求的话,应当使用Redis Cluster这么一个高可用的集群方案。

参考资料