redis 哨兵机制

在主从同步的基础上,为了使 redis 故障恢复更加快捷,Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。

搭建哨兵集群

具体搭建流程参考课前资料 《Redis集群方案.md

哨兵原理

集群结构和作用

哨兵的结构如图:

image-20210725154528072

哨兵的作用如下:

  • 监控:Sentinel 会不断检查您的 master 和 slave 是否按预期工作
  • 自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master。当故障实例恢复后也以新的 master 为主
  • 通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端
  • 配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。

其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移;而配置提供者和通知功能,则需要在与客户端的交互中才能体现

哨兵集群通信原理

上图中哨兵集群是如何组建的呢?哨兵实例之间可以相互发现,要归功于 Redis 提供的 pub/sub 机制,也就是发布 / 订阅机制。

在主从集群中,master 上有一个名为__sentinel__:hello的频道,不同哨兵就是通过它来相互发现,实现互相通信的。哨兵 A 会把自己的 IP和端口发布到该频道上,其他哨兵会订阅该频道,那么其他的哨兵就会获取到哨兵 A 的ip和端口,从而发现哨兵 A 并且与其建立起网络连接 ,这也是为什么在搭建哨兵时配置某一个哨兵不需要配置其他哨兵地址和端口的原因。

以此类推,其他的哨兵也可以被发现和被建立连接,这样一来,哨兵机制就形成了。他们就可以开始通信

监视slave原理

我们都知道,在配置 哨兵配置文件时,我们只是配置了master 节点的 ip 和端口,那么哨兵是如何发现该master的slave节点的呢?其实,这是由哨兵向 master 发送 INFO 命令来完成的。哨兵 A 给 master 发送 INFO 命令,master 接受到这个命令后,就会把slave 列表返回给哨兵。接着,哨兵就可以根据 slave 列表中的连接信息,和每个 slave 建立连接,并在这个连接上持续地对 slave 进行监控。

集群监控原理

Sentinel 基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线

客观下线:若超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例 客观下线 。quorum值最好超过Sentinel实例数量的一半。

当某个哨兵(如下图中的哨兵2)判断主库“主观下线”后,就会给其他哨兵发送 is-master-down-by-addr 命令。接着,其他哨兵会根据自己和主库的连接情况,做出 Y 或 N 的响应,Y 相当于赞成票,N 相当于反对票。

image-20210725154632354

如果赞成票数是大于等于哨兵配置文件中的 quorum 配置项(比如这里如果是quorum=2), 则可以判定 master客观下线了。

哨兵leader的选举

判断完主库下线后,由哪个哨兵节点来执行主从切换呢?这里就需要哨兵集群的选举机制了。

  • 为什么必然会出现选举/共识机制

为了避免哨兵的单点情况发生,所以需要一个哨兵的分布式集群。作为分布式集群,必然涉及共识问题(即选举问题);同时故障的转移和通知都只需要一个主的哨兵节点就可以了。

  • 哨兵的选举机制是什么样的

哨兵的选举机制其实很简单,就是一个 Raft 选举算法: 选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续选举

  • 任何一个想成为 Leader 的哨兵,要满足两个条件:
    • 第一,拿到半数以上的赞成票;
    • 第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。

故障恢复原理

选出新的 master

一旦发现 master 故障,sentinel 需要在 salve 中选择一个作为新的 master,选择依据是这样的:

  • 首先会判断 slave 节点与 master 节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
  • 然后判断 slave 节点的 slave-priority 值,越小优先级越高,如果是0则永不参与选举
  • 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大说明数据越新,优先级越高
  • 最后是判断 slave 节点的运行 id 大小,越小优先级越高。

切换 master

流程如下:

  • sentinel 给被选中的 slave 节点发送 slaveof no one命令,让该节点成为master
  • sentinel 给所有其它 slave 发送 slaveof masterIP masterPort 命令,让这些 slave 成为新 master 的从节点,开始从新的 master上同步数据。
  • 最后,sentinel 将故障节点标记为 slave,当故障节点恢复后会自动成为新的 master 的 slave 节点

image-20210725154816841

RedisTemplate

在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用 lettuce 实现了节点的感知和自动切换。

下面,我们通过一个测试来实现RedisTemplate集成哨兵机制。

引入依赖

在项目的pom文件中引入依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置Redis地址

然后在配置文件application.yml中指定redis的sentinel相关信息:

1
2
3
4
5
6
7
8
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003

配置读写分离

在项目的启动类中,添加一个新的bean:

1
2
3
4
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}

这个bean中配置的就是读写策略,包括四种:

  • MASTER:从主节点读取
  • MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
  • REPLICA:从slave(replica)节点读取
  • REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master