Docker 部署 Redis 哨兵模式

HYF Lv3

本文将介绍如何使用 Docker 快速部署 Redis 哨兵模式。

前言

Redis 的主从复制和哨兵机制是实现高可用性和读写分离的关键。通过配置主从复制,我们可以实现数据备份和读取负载均衡;而哨兵机制则提供了自动故障转移和监控功能。将 Redis 的主从、哨兵配置与 Spring Boot 集成,可以实现读写分离,提高系统性能和可靠性。

本文将详细介绍如何使用 Docker 部署 Redis 哨兵模式,模拟 Redis 的高可用性。

主从复制(Master-Slave Replication)

在深入了解和使用 Redis 哨兵机制之前,先了解 Redis 的主从复制机制是很有必要的。主从复制是 Redis 提供的一种基本高可用性解决方案,它是哨兵机制的基础。

Redis 主从复制(Master-Slave Replication)是一种用于实现数据冗余和高可用性的机制。在这种机制中,一个 Redis 实例作为主节点(Master),负责处理所有的写操作;而一个或多个 Redis 实例作为从节点(Slave),负责从主节点复制数据,并处理读取操作。

高可用性:主从复制提供了一种简单的高可用性方案。如果主节点出现故障,可以手动提升一个从节点为新的主节点。

读写分离:通过将读取操作分配给从节点,可以有效地分担主节点的压力,提高系统的读性能。

数据备份:从节点提供了数据冗余,防止单点故障导致数据丢失。

故障恢复需人工干预:在主节点故障时,需手动将一个从节点提升为主节点,恢复时间较长

容灾能力:如果所有从节点挂掉,那么这个集群的容灾能力就很差了

哨兵机制(Redis Sentinel)

Redis Sentinel,即Redis哨兵,在Redis 2.8版本开始引入。哨兵的核心功能是主节点的自动故障转移。
哨兵实现了什么功能呢?下面是 Redis 官方文档的描述:

监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。

自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。

配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。

通知(Notification):哨兵可以将故障转移的结果发送给客户端。

在哨兵机制中,监控自动故障转移功能是其核心特性。哨兵通过持续监控主从节点的健康状态,能够及时发现主节点故障,并迅速完成故障转移,将一个从节点提升为新的主节点,确保系统的高可用性。

此外,哨兵还提供了配置提供者功能,维护主从节点的配置信息,并使客户端能够获取最新的主节点地址,保证数据的读写能够无缝切换到新的主节点。同时,哨兵支持通知功能,当主节点发生故障或进行故障转移时,能够通过邮件、短信或日志等方式及时通知运维人员,便于快速响应和恢复服务。

哨兵模式的部署

Docker 的安装与部署,本文不再赘述,又需要可以查询官网或者参考往期BlogDocker 安装部署

最佳实践

本文将部署 3 个哨兵节点、1 个主节点和 2 个从节点,共 6 个节点在同一台服务器上。这种部署方式主要用于演示如何设置哨兵模式,但并不是最佳实践。如果服务器发生故障,所有 Redis 实例(包括主节点、从节点和哨兵节点)都会不可用,无法实现高可用性。

推荐的部署做法
采用分布式部署,建议将主从节点分别部署在不同的服务器上,这样即使一台服务器宕机,其他的节点依然可以继续工作,哨兵节点同理,且哨兵节点至少 3 个,且数量为奇数,避免选举新主节点时出现平票的情况。

实践示例

  • 服务器 A
    • 主节点(Master
    • 哨兵节点 1
  • 服务器 B
    • 从节点(Slave 1
    • 哨兵节点 2
  • 服务器 C
    • 从节点(Slave 2
    • 哨兵节点 3

本文部署的哨兵模式拓扑图
(别问,问就是懒得画图)

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
+--------------------+                 +--------------------+                 +--------------------+
| | | | | |
| Sentinel 1 | | Sentinel 2 | | Sentinel 3 |
| | | | | |
| IP: 172.24.0.5 | | IP: 172.24.0.6 | | IP: 172.24.0.7 |
| Port: 16379 | | Port: 16380 | | Port: 16381 |
| | | | | |
+---------|----------+ +---------|----------+ +---------|----------+
| | |
| | |
+--------------------------------------+--------------------------------------+
|
|
|
+------------------------|-------------------------+
| | |
| | |
+--------|---------+ +--------|---------+ +---------|---------+
| | | | | | | | |
| Master | | Slave 1 | | Slave 2 |
| | | | | |
| IP: 172.24.0.2 | | IP: 172.24.0.3 | | IP: 172.24.0.4 |
| Port: 6379 | | Port: 6380 | | Port: 6381 |
| | | | | |
+------------------+ +------------------+ +-------------------+

部署主从服务

在正式部署之前,我们先创建好必要的文件夹。我们需要创建 sentinelserver 两个文件夹。sentinel 文件夹将用于存放哨兵模式的相关配置文件,而 server 文件夹则用于部署 Redis 主从服务的文件。之后部署的 sentinelredis-server将分别用两个 docker-compose 来管理。

创建文件夹

1
2
mkdir -p /mnt/docker/redis/sentinel /mnt/docker/redis/server
cd /mnt/docker/redis/server/

本小节将要部署 Redis 主从服务,所以我们移动到我们的 server 文件夹中。

我们需要先配置好 Redis 主从服务的一些简单配置:

redis-master.conf

1
2
3
4
5
6
7
port 6379

always-show-logo yes

requirepass [your master password]

rename-command KEYS ""

redis-slave1.conf

1
2
3
4
5
6
7
8
9
10
11
port 6380

always-show-logo no

requirepass [your slave1 password]

rename-command KEYS ""

slaveof redis-server-master 6379

masterauth [your master password]

redis-slave2.conf

1
2
3
4
5
6
7
8
9
10
11
port 6381

always-show-logo no

requirepass [your slave2 password]

rename-command KEYS ""

slaveof redis-server-master 6379

masterauth [your master password]

docker-compose.yml

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
40
41
42
43
services:
redis-server-master:
image: redis
container_name: redis-server-master
restart: always
network_mode: host
ports:
- 6379:6379
environment:
TZ: "Asia/Shanghai"
volumes:
- ./redis-master.conf:/usr/local/etc/redis/redis.conf
- ../data/redis-master:/data
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
redis-server-slave-1:
image: redis
container_name: redis-server-slave-1
restart: always
network_mode: host
ports:
- 6380:6380
depends_on:
- redis-server-master
environment:
TZ: "Asia/Shanghai"
volumes:
- ./redis-slave1.conf:/usr/local/etc/redis/redis.conf
- ../data/redis-slave-1:/data
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
redis-server-slave-2:
image: redis
container_name: redis-server-slave-2
restart: always
ports:
- 6381:6381
depends_on:
- redis-server-master
environment:
TZ: "Asia/Shanghai"
volumes:
- ./redis-slave2.conf:/usr/local/etc/redis/redis.conf
- ../data/redis-slave-2:/data
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
在我们完成以上操作后,在 server 文件夹下应有以下四个文件。接下来使用 Docker compose up -d 命令,启动我们的主从服务。

主从服务的启动

在服务成功启动后,我们使用 Docker compose logs 来查看日志,我们可以得到以下关键信息:
1
2
3
4
Connecting to MASTER redis-server-master:6379
MASTER <-> REPLICA sync started
Full resync from master: ...
MASTER <-> REPLICA sync: Finished with success
这段日志告诉我们主从服务已经开始同步。
1
WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

这段警告是在说,所有节点(包括主节点和从节点)都提示需要启用内存过度提交(vm.overcommit_memory)以避免在内存不足情况下的故障。

解决方法是将 vm.overcommit_memory = 1 添加到 /etc/sysctl.conf,然后重启系统或运行 sysctl vm.overcommit_memory=1 使其生效。
1
2
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl -p

(如果在没有添加过该参数的情况下,我们可以使用以上命令直接在该配置文件中追加该配置信息,并运行 sysctl -p 使更改生效。不确定的情况下只能老老实实 vim 该文件手动添加了。)

执行完上述命令后,重启 docker compose,并查看日志,我们可以发现,该警告被解决了。

警告被解决

接下来我们进入到 Redis 主节点,查看是否真的部署成功:
查看主从信息

由图片可知,当前节点为 Master 主节点,且有2个 Slave 从节点。截止目前为止,我们的主从服务已经部署好了。

部署哨兵模式

接下来,我们移动到我们的 sentinel 文件夹中,并配置以下三个哨兵配置文件以及 docker-compose.yml 文件

redis-sentinel-1.conf

1
2
3
4
5
6
7
8
9
port 16379

requirepass [your sentinel-1 password]

sentinel monitor local-master [your IP | redis IP] 6379 2

sentinel auth-pass local-master [your master password]

sentinel down-after-milliseconds local-master 10000

配置参数解释:

sentinel monitor

  • master-name 是为这个被监控的 master 起的名字
  • ip 是被监控的 masterIP 或主机名。因为 Docker 容器之间可以使用容器名访问,所以这里写 master 节点的容器名
  • redis-port 是被监控节点所监听的端口号
  • quorom 设定了当几个哨兵判定这个节点失效后,才认为这个节点真的失效了

sentinel auth-pass

  • 连接主节点的密码

sentinel down-after-milliseconds

  • master 在连续多长时间无法响应 PING 指令后,就会主观判定节点下线,默认是30秒

接下来的 redis-sentinel-2.confredis-sentinel-3.confredis-sentinel-1.confport 参数有所改变,所以我们可以直接复制该文件,然后修改第一行的端口号即可:

1
2
3
4
cp redis-sentinel-1.conf redis-sentinel-2.conf
sed '1s/16379/16380/' redis-sentinel-2.conf
cp redis-sentinel-1.conf redis-sentinel-3.conf
sed '1s/16379/16381/' redis-sentinel-3.conf

sentinel配置文件
docker-compose.yml

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
40
41
42
43
44
services:
redis-sentinel-1:
image: redis
container_name: redis-sentinel-1
restart: always
ports:
- 26379:26379
volumes:
- ./redis-sentinel-1.conf:/usr/local/etc/redis/conf/redis-sentinel.conf
environment:
TZ: "Asia/Shanghai"
sysctls:
net.core.somaxconn: '511'
command: ["redis-sentinel", "/usr/local/etc/redis/conf/redis-sentinel.conf"]
redis-sentinel-2:
image: redis
container_name: redis-sentinel-2
restart: always
ports:
- 26380:26380
volumes:
- ./redis-sentinel-2.conf:/usr/local/etc/redis/conf/redis-sentinel.conf
environment:
TZ: "Asia/Shanghai"
sysctls:
net.core.somaxconn: '511'
command: ["redis-sentinel", "/usr/local/etc/redis/conf/redis-sentinel.conf"]
redis-sentinel-3:
image: redis
container_name: redis-sentinel-3
restart: always
ports:
- 26381:26381
volumes:
- ./redis-sentinel-3.conf:/usr/local/etc/redis/conf/redis-sentinel.conf
environment:
TZ: "Asia/Shanghai"
sysctls:
net.core.somaxconn: '511'
command: ["redis-sentinel", "/usr/local/etc/redis/conf/redis-sentinel.conf"]
networks:
default:
name: server_default
external: true

在我们完成上述操作后,接下来我们使用 docker compose up -d 命令启动哨兵。

在服务成功启动后,我们使用 Docker compose logs 来查看日志,我们可以得到以下信息:
1
+slave slave 172.24.0.3:6380 172.24.0.3 6380 @ local-master 172.24.0.2 6379

+slave 表示一个新的从节点(slave)被添加到监控的主节点(master)。这是哨兵检测到并确认从节点的日志消息。

1
+monitor master local-master 172.24.0.2 6379 quorum 2

+monitor 表示 Redis Sentinel 开始监控一个新的主节点(master)。这是哨兵开始监控指定主节点的日志消息。

1
+sentinel sentinel 9ed8fdfd98be554eecd237b46f98d21ba642b7ef 172.24.0.5 16379 @ local-master 172.24.0.2 6379

+sentinel 表示一个新的哨兵节点(sentinel)被检测到并加入监控。这是一个哨兵节点发现其他哨兵节点并开始与它们协作的日志消息。

到目前为止,我们的 Redis 哨兵模式已经部署完成了。

哨兵模式的验证

我们先来查看以下我们的 Master 节点情况:

Master情况
可以看到,当前的主节点为 port 为 6379 的 Master 节点,且支持读写数据

而下面这个是我们的一个 Slave 节点:

Slave情况
可以看到,目前这个port 为 6380 的节点为 Slave 节点,且仅支持读,不可写入数据

接下来我们将 Master 节点关闭来模拟宕机。

停止Master

此刻回到我们的 /sentinel 文件夹中查看我们的哨兵日志

我们来逐一解读重要日志:

1
2
+sdown master local-master 139.159.161.97 6379
+odown master local-master 139.159.161.97 6379 #quorum 3/2

Sentinel 检测到主节点 local-master 下线,达到了配额要求,确认主节点处于下线状态。

1
2
+new-epoch 1
+vote-for-leader 4f98152c1195b018258926975a38892b658b688c 1

Sentinel 开始了新的投票周期,并投票选举新的领导者。

1
+switch-master local-master 139.159.161.97 6379 139.159.161.97 6380

Sentinel 将新的主节点切换为 139.159.161.97:6380。

从日志上可以看出,当检测到我们的主节点在”宕机”之后,我们的哨兵 Sentinel 首先将其标记为主观下线(+sdown master),并在多个 Sentinel 达成共识,即达到仲裁配额,3个 Sentinel 中有2个同意,则将确认主节点确实不可用(+odown master)
接下来,Sentinel 开始一个新的投票周期,又称新纪元(epoch),以选举新的主节点(+vote-for-leader),并将合适的从节点作为新的主节点(+switch-master local-master

我们来看一下我们 Redis 各节点的情况,由于刚刚 Master 被我们模拟宕机,所以自然是登陆不上去的。而由日志可知,port 为 6380 的 Slave 节点被选为新的 Master 节点,我们来验证一下
新的Master
由图可知,主节点依照我们的设想和意愿,成功切换为非故障节点,且具有读写数据功能。自此,Redis 哨兵模式的验证结束。

哨兵模式在 Spring Boot 中读写分离和负载均衡的简单应用

首先我们需要在 maven 中添加以下依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--springboot中的redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lettuce pool 缓存连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 使用jackson作为redis数据序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
接下来配置一下我们的 applycation.yml
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
spring:
redis:
sentinel:
master: local-master
nodes:
- 139.159.161.97:6379
- 139.159.161.97:6380
- 139.159.161.97:6381
database: 0
password: [redis-password]
timeout: 5000
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 1
shutdown-timeout: 1000ms
# 打印lettuce debug日志,方便查看读写分离效果
logging:
pattern:
console: '%date{yyyy-MM-dd HH:mm:ss.SSS} | %highlight(%5level) [%green(%16.16thread)] %clr(%-50.50logger{49}){cyan} %4line -| %highlight(%msg%n)'
level:
root: info
io.lettuce.core: debug
org.springframework.data.redis: debug

接下来在 RedisConfig.Java 中添加以下配置来实现读写分离:

1
2
3
4
5
6
7
8
9
10
@Bean
public RedisConnectionFactory lettuceConnectionFactory(RedisProperties redisProperties) {
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes())
);
LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
.readFrom(ReadFrom.ANY_REPLICA)
.build();
return new LettuceConnectionFactory(redisSentinelConfiguration, lettuceClientConfiguration);
}

ReadFrom 配置中,我们将其配置为 ANY_REPLICA,意味着读取操作可以从任何可用的从节点读取数据。如果没有从节点可用,则会从主节点读取。可以有效地分散读取负载,提高读操作的并发性和性能。
ReadFrom 枚举类型的其他选项:

  • MASTER:只从主节点读取。
  • MASTER_PREFERRED:优先从主节点读取,如果主节点不可用,则从从节点读取。
  • ANY:从任何节点读取,包括主节点和从节点。
  • ANY_REPLICA:只从从节点读取,如果从节点不可用,则从主节点读取。
  • NEAREST:从最近的节点读取。
  • REPLICA:只从从节点读取。
  • REPLICA_PREFERRED:优先从从节点读取,如果从节点不可用,则从主节点读取。

到这里,我们的 Spring Boot 配置哨兵模式及读写分离就配置完成了。

结语

在本文中,我们介绍了如何配置和使用 Redis Sentinel 以实现 Redis 集群的高可用性。我们从 Sentinel 的基本概念出发,简单分析了主节点故障时 Sentinel 的处理流程,涵盖了从节点标记为主观下线、共识达成、新纪元的启动以及主节点切换等步骤。以及简单地配置了一个 Spring BootRedis Sentinel,希望通过这些简单的配置和实践,读者能够更深入的理解 Redis Sentinel

  • 标题: Docker 部署 Redis 哨兵模式
  • 作者: HYF
  • 创建于 : 2024-06-17 23:56:33
  • 更新于 : 2024-07-27 21:21:51
  • 链接: https://youfeng.ink/redis-sentinel-d16ccfb50660/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。