🌟 后端 | Redis 哨兵



Redis 提供了哨兵(Sentinel)机制来监控主从集群监控状态,确保集群的高可用性。

哨兵的作用如下:

  • 状态监控:Sentinel 会不断检查您的 master 和 slave 是否按预期工作
  • 故障恢复(failover):如果 master 故障,Sentinel 会将一个 slave 提升为 master。当故障实例恢复后会成为 slave
  • 状态通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生failover 时,会将最新集群信息推送给 Redis 的客户端

状态监控

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个节点发送 ping 命令,并通过实例的响应结果来做出判断:

  • 主观下线(sdown):如果某 sentinel 节点发现某Redis节点未在规定时间响应,则认为该节点主观下线。
  • 客观下线(odown):若超过指定数量(通过quorum设置)的 sentinel 都认为该节点主观下线,则该节点客观下线。quorum 值最好超过 Sentinel 节点数量的一半,Sentinel 节点数量至少3台。

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

  • 首先会判断slave节点与master节点断开时间长短,如果超过down-after-milliseconds * 10则会排除该slave节点
  • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举(默认都是1)。
  • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
  • 最后是判断slave节点的run_id大小,越小优先级越高(通过info server可以查看run_id)。

选举 leader

当选出一个新的 master 后,需要进行身份切换。

Sentinel 集群要选出一个执行 failover 的 Sentinel 节点,可以成为leader。要成为 leader 要满足两个条件:

  • 最先获得超过半数的投票
  • 获得的投票数不小于quorum值

而 sentinel 投票的原则有两条:

  • 优先投票给目前得票最多的
  • 如果目前没有任何节点的票,就投给自己

failover

For example, there are master, slave1 and slave2 in a group.

when master doesn’t work, sentinel will send the command ‘slaveof no one’ to slave1, and make it become master.

Then sentinel sends the command ‘slaveof ip port’ to other slaves, and make these nodes update their master before synchronizing data from master.

Finally, sentinel executes the command ‘slaveof ip port’ until the bad node recovers.

搭建哨兵集群

配置 sentinel.conf

1
2
3
4
sentinel announce-ip "192.168.150.101"
sentinel monitor hmaster 192.168.150.101 7001 2
sentinel down-after-milliseconds hmaster 5000
sentinel failover-timeout hmaster 60000
  • sentinel announce-ip “192.168.150.101”:声明当前sentinel的ip
  • sentinel monitor hmaster 192.168.150.101 7001 2:指定集群的主节点信息
    • hmaster:主节点名称,自定义,任意写
    • 192.168.150.101 7001:主节点的ip和端口
    • 2:认定master下线时的quorum值
  • sentinel down-after-milliseconds hmaster 5000:声明master节点超时多久后被标记下线
  • sentinel failover-timeout hmaster 60000:在第一次故障转移失败后多久再次重试

需要在 /root/redis 下创建 3 个文件夹:s1, s2, s3, 将sentinel.conf 分别拷贝一份至3个文件夹

修改 docker-compose.yaml 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
version: "3.2"

services:
r1:
image: redis
container_name: r1
network_mode: "host"
entrypoint: ["redis-server", "--port", "7001"]
r2:
image: redis
container_name: r2
network_mode: "host"
entrypoint: ["redis-server", "--port", "7002", "--slaveof", "192.168.150.101", "7001"]
r3:
image: redis
container_name: r3
network_mode: "host"
entrypoint: ["redis-server", "--port", "7003", "--slaveof", "192.168.150.101", "7001"]
s1:
image: redis
container_name: s1
volumes:
- /root/redis/s1:/etc/redis
network_mode: "host"
entrypoint: ["redis-sentinel", "/etc/redis/sentinel.conf", "--port", "27001"]
s2:
image: redis
container_name: s2
volumes:
- /root/redis/s2:/etc/redis
network_mode: "host"
entrypoint: ["redis-sentinel", "/etc/redis/sentinel.conf", "--port", "27002"]
s3:
image: redis
container_name: s3
volumes:
- /root/redis/s3:/etc/redis
network_mode: "host"
entrypoint: ["redis-sentinel", "/etc/redis/sentinel.conf", "--port", "27003"]

启动集群:

1
docker-compose up -d

启动后可以看到哨兵 sentinel 联系到 master 节点,与其它几个哨兵也建立了链接。

连接7001这个master节点,然后通过命令让其休眠60秒,模拟宕机:

1
2
# 连接7001这个master节点,通过sleep模拟服务宕机,60秒后自动恢复
docker exec -it r1 redis-cli -p 7001 DEBUG sleep 60

可以发现 s1 节点率先发现 7001 主观下线,时间落后 s2, s2 投票最多,从 s2 更新

RedisTemplate 连接哨兵集群

引入依赖

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

配置哨兵地址

连接哨兵集群与传统单点模式不同,不再需要设置每一个redis的地址,而是直接指定哨兵地址

1
2
3
4
5
6
7
8
spring:
redis:
sentinel:
master: hmaster # 集群名
nodes: # 哨兵地址列表
- 192.168.150.101:27001
- 192.168.150.101:27002
- 192.168.150.101:27003

配置读写分离

还要配置读写分离,让 java 客户端将写请求发送到 master 节点,读请求发送到 slave 节点。定义一个 bean 即可:

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

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

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