本文着力介绍如何实现 Redis Sentinel 模式, 从而实现高可用. 整体架构由 4 个 Redis Instance 和 3 个 Redis Sentinel 组成. 并介绍如何与 SpringBoot 集成.
工作机制
Redis Sentinel (哨兵) 是官方推荐的高可用解决方案, 当 Redis 在做 master-slave 的高可用方案时, 如果 master 宕机了, Redis 本身没有实现自动主备切换的特性, Sentinel 可以做到这一点.
Redis Sentinel 本身也是独立运行的进程, 可以部署在其他与 Redis 集群 (Cluster) 可通讯的机器中监控 Redis 集群.
- 监控: Sentinel 能够持续的监听你的主备节点是否正常运行.
- 通知: 当被监听的节点出现异常状况的时候, Sentinel 能够通知系统管理员, 或者通知应用程序 (通过 API).
- 自动的故障转移: 如果主节点没有按照预期正常运行, Sentinel 会启动故障转移流程, 备用节点会被提升成主节点, 其他备用节点也会被重新配置以从新的主节点备份数据. 对接了 Redis 的应用程序也会被通知到切换到新的主节点的连接地址.
- 配置提供: Sentinel 表现的像是 Redis Instance 的服务发现组件, 客户端连接 Sentinel 以获得当前主节点实例的地址, 如果故障转移流程启动, Sentinel 则会反馈新的主节点地址.
Redis Sentinel 本身就被设计作为一个分布式系统, 与其他 Sentinel 协同工作, 这样的好处有:
- 投票机制: 当多个 Sentinel 同意当前主节点已经不再可用的时候, 执行主备切换 (少数服从多数).
- 即使 Sentinel 集群本身有节点不可用, 也不妨碍其他 Sentinel 正常运行.
当 Sentinel 集群中多数 (如果节点数为 3, 这个阈值就应该是 2) Sentinel 都认为当前 master 故障, 才会进行主备切换, 投票将剩下的备用节点中的其中一台提升为 master 节点, 并自动修改 redis.conf 配置文件, 使其余备用节点响应新的 master.
环境搭建
首先, 你的机器上应该已经安装 Redis, 参考: CentOS 7 编译 安装 Redis-5.0.2
Redis Instance 配置
① 在 /usr/local 下新建一个目录 redis-5.0.2-sentinel:
mkdir redis-5.0.2-sentinel
② 我们占用 7501 ~ 7507 7 个端口, 其中 7501 ~ 7504 4 个端口为 Redis Instance 集群, 包含 1 主 3 从, 7505 ~ 7507 3 个端口为 Redis Sentinel 集群, 包含 1 主 2 从. 在该目录下新建 7 个文件夹:
mkdir /usr/local/redis-5.0.2-sentinel/{7501,7502,7503,7504,7505,7506,7507}
预期文件结构如下:
./redis-5.0.2-sentinel/
├── 7501
│ └── redis-7501.conf
├── 7502
│ └── redis-7502.conf
├── 7503
│ └── redis-7503.conf
├── 7504
│ └── redis-7504.conf
├── 7505
│ └── sentinel-7505.conf
├── 7506
│ └── sentinel-7506.conf
└── 7507
└── sentinel-7507.conf
③ 依次从之前安装的 Redis 目录下拷贝 redis.conf 至 /usr/local/redis-5.0.2-sentinel/7501/redis-7501.conf, /usr/local/redis-5.0.2-sentinel/7502/redis-7502.conf, /usr/local/redis-5.0.2-sentinel/7503/redis-7503.conf, /usr/local/redis-5.0.2-sentinel/7504/redis-7504.conf:
cp ./redis-5.0.2/redis.conf ./redis-5.0.2-sentinel/7501/redis-7501.conf
cp ./redis-5.0.2/redis.conf ./redis-5.0.2-sentinel/7502/redis-7502.conf
cp ./redis-5.0.2/redis.conf ./redis-5.0.2-sentinel/7503/redis-7503.conf
cp ./redis-5.0.2/redis.conf ./redis-5.0.2-sentinel/7504/redis-7504.conf
④ 依次更改配置文件, 主要涉及端口, 日志文件, 工作目录等配置, 以 7501 举例:
port=7501
# 支持后台 (守护进程) 的方式运行
daemonize=yes
pidfile=/var/run/redis_7501.pid
# 需要注意的是: 如果设置成 ./redis-7501.log, 则实际日志文件的存放位置是相对路径, 相对的是你执行 redis-server <the-path-of-config-file> 命令的路径
logfile="/usr/local/redis-5.0.2-sentinel/7501/redis-7501.log"
dir="/usr/local/redis-5.0.2-sentinel/7501"
# 当前实例的密钥
requirepass=redis-cluster-pass
# master 节点的密钥, 由于要执行主备切换, requirepass 和 masterauth 应该一致
masterauth=redis-cluster-pass
appendonly=yes
⑤ 分别启动 4 个 Redis Instance:
redis-server /usr/local/redis-5.0.2-sentinel/7501/redis-7501.conf
redis-server /usr/local/redis-5.0.2-sentinel/7502/redis-7502.conf
redis-server /usr/local/redis-5.0.2-sentinel/7503/redis-7503.conf
redis-server /usr/local/redis-5.0.2-sentinel/7504/redis-7504.conf
⑥ 设置主从关系, 也可以在 redis-<port>.conf 中通过 replicaof 配置:
[root@VM-0-9-centos ~]# redis-cli -h 127.0.0.1 -p 7502
127.0.0.1:7502> auth redis-cluster-pass
OK
127.0.0.1:7502> SLAVEOF 127.0.0.1 7501
OK
127.0.0.1:7502> exit
[root@VM-0-9-centos ~]# redis-cli -h 127.0.0.1 -p 7503
127.0.0.1:7503> auth redis-cluster-pass
OK
127.0.0.1:7503> SLAVEOF 127.0.0.1 7501
OK
127.0.0.1:7503> exit
[root@VM-0-9-centos ~]# redis-cli -h 127.0.0.1 -p 7504
127.0.0.1:7504> auth redis-cluster-pass
OK
127.0.0.1:7504> SLAVEOF 127.0.0.1 7501
OK
127.0.0.1:7504> exit
[root@VM-0-9-centos ~]#
⑦ cli 登陆 master 节点, 查看 Redis Instance 状态:
[root@VM-0-9-centos redis-5.0.2-sentinel]# redis-cli -h 127.0.0.1 -p 7501
127.0.0.1:7501> auth redis-cluster-pass
OK
127.0.0.1:7501> info replication
# Replication
role:master
connected_slaves:3
slave0:ip=127.0.0.1,port=7502,state=online,offset=644,lag=1
slave1:ip=127.0.0.1,port=7503,state=online,offset=644,lag=1
slave2:ip=127.0.0.1,port=7504,state=online,offset=644,lag=1
master_replid:6d8140a2963cbc57a7799c2300d9c08f125d4924
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:644
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:644
127.0.0.1:7501>
Redis Sentinel 配置
① 将之前安装文件夹下的 sentinel.conf 一次拷贝到 /usr/local/redis-5.0.2-sentinel/7505/sentinel-7505.conf, /usr/local/redis-5.0.2-sentinel/7506/sentinel-7506.conf, /usr/local/redis-5.0.2-sentinel/7507/sentinel-7507.conf:
cp ./redis-5.0.2/sentinel.conf ./redis-5.0.2-sentinel/7505/sentinel-7505.conf
cp ./redis-5.0.2/sentinel.conf ./redis-5.0.2-sentinel/7506/sentinel-7506.conf
cp ./redis-5.0.2/sentinel.conf ./redis-5.0.2-sentinel/7507/sentinel-7507.conf
② 修改配置文件, 以 7505 为例:
# 需要在 Sentinel 的配置文件中指定 Sentinel 的密钥
# 如果不做这个操作, 在 SpringBoot 集成的时候, Redis Instance 有密钥保护, 而 Redis Sentinel 没有:
# application.yml 如果不设置密钥, 会报错: NOAUTH Authentication required.
# application.yml 设置了密钥又囧报: ERR Client sent AUTH, but no password is set
requirepass="redis-cluster-pass"
port=7505
daemonize=yes
pidfile=/var/run/redis-sentinel-7505.pid
logfile="/usr/local/redis-5.0.2-sentinel/7505/sentinel-7505.log"
dir="/usr/local/redis-5.0.2-sentinel/7505"
# 默认将主节点设置为 7501,
# redis-5.0.2-master 是别名;
# 2 表示只要有 2 个 Sentinel 赞同, 就执行故障转移 (主备切换), 这个数字不能大于 Sentinel 的个数
sentinel=monitor redis-5.0.2-master 127.0.0.1 7501 2
# 主节点的访问信息
sentinel=auth-pass redis-5.0.2-master redis-cluster-pass
③ 启动 3 个哨兵:
[root@VM-0-9-centos redis-5.0.2-sentinel]# redis-sentinel 7505/sentinel-7505.conf
[root@VM-0-9-centos redis-5.0.2-sentinel]# redis-sentinel 7506/sentinel-7506.conf
[root@VM-0-9-centos redis-5.0.2-sentinel]# redis-sentinel 7507/sentinel-7507.conf
[root@VM-0-9-centos redis-5.0.2-sentinel]# ps -ef | grep redis
root 6625 1 0 01:09 ? 00:00:00 redis-server 0.0.0.0:7501
root 6644 1 0 01:09 ? 00:00:00 redis-server 0.0.0.0:7502
root 6664 1 0 01:09 ? 00:00:00 redis-server 0.0.0.0:7503
root 6677 1 0 01:09 ? 00:00:00 redis-server 0.0.0.0:7504
root 7694 1 0 01:15 ? 00:00:00 redis-sentinel *:7505 [sentinel]
root 7714 1 0 01:15 ? 00:00:00 redis-sentinel *:7506 [sentinel]
root 7744 1 0 01:15 ? 00:00:00 redis-sentinel *:7507 [sentinel]
root 7841 27334 0 01:16 pts/0 00:00:00 grep --color=auto redis
[root@VM-0-9-centos redis-5.0.2-sentinel]#
测试
① 登陆主节点, 设置一个 KeyValue, 可以看到从节点也有对应的记录 (默认主节点可读写, 从节点只可读):
[root@VM-0-9-centos local]# redis-cli -h 127.0.0.1 -p 7501
127.0.0.1:7501> auth redis-cluster-pass
OK
127.0.0.1:7501> set foo bar
OK
127.0.0.1:7501> exit
[root@VM-0-9-centos local]# redis-cli -h 127.0.0.1 -p 7503
127.0.0.1:7503> get foo
(error) NOAUTH Authentication required.
127.0.0.1:7503> auth redis-cluster-pass
OK
127.0.0.1:7503> get foo
"bar"
127.0.0.1:7503> exit
② 接下来我们 kill 掉主节点试试:
[root@VM-0-9-centos ~]# ps -ef | grep redis
root 6625 1 0 01:09 ? 00:00:23 redis-server 0.0.0.0:7501
root 6644 1 0 01:09 ? 00:00:22 redis-server 0.0.0.0:7502
root 6664 1 0 01:09 ? 00:00:22 redis-server 0.0.0.0:7503
root 6677 1 0 01:09 ? 00:00:22 redis-server 0.0.0.0:7504
root 7694 1 0 01:15 ? 00:00:42 redis-sentinel *:7505 [sentinel]
root 7714 1 0 01:15 ? 00:00:42 redis-sentinel *:7506 [sentinel]
root 7744 1 0 01:15 ? 00:00:42 redis-sentinel *:7507 [sentinel]
root 29385 29280 0 09:43 pts/0 00:00:00 grep --color=auto redis
[root@VM-0-9-centos ~]# kill -9 6625
[root@VM-0-9-centos ~]# ps -ef | grep redis
root 6644 1 0 01:09 ? 00:00:22 redis-server 0.0.0.0:7502
root 6664 1 0 01:09 ? 00:00:22 redis-server 0.0.0.0:7503
root 6677 1 0 01:09 ? 00:00:22 redis-server 0.0.0.0:7504
root 7694 1 0 01:15 ? 00:00:42 redis-sentinel *:7505 [sentinel]
root 7714 1 0 01:15 ? 00:00:42 redis-sentinel *:7506 [sentinel]
root 7744 1 0 01:15 ? 00:00:42 redis-sentinel *:7507 [sentinel]
root 30638 29280 0 09:50 pts/0 00:00:00 grep --color=auto redis
[root@VM-0-9-centos ~]# redis-cli -h 127.0.0.1 -p 7502
127.0.0.1:7502> auth redis-cluster-pass
OK
127.0.0.1:7502> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7504
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:6511835
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:c60c6880cb9054c543a8472adf73432df74bf20b
master_replid2:aa1551df9635dcc155e510aaa6712cda6a7e7bff
master_repl_offset:6511835
second_repl_offset:6506247
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:5463260
repl_backlog_histlen:1048576
127.0.0.1:7502>
可以看到 7504 已经被提升为 master 了.
③ 接下来再次启动之前的主节点:
[root@VM-0-9-centos ~]# redis-server /usr/local/redis-5.0.2-sentinel/7501/redis-7501.conf
[root@VM-0-9-centos ~]# redis-cli -h 127.0.0.1 -p 7501
127.0.0.1:7501> auth redis-cluster-pass
OK
127.0.0.1:7501> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7504
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:6525162
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:c60c6880cb9054c543a8472adf73432df74bf20b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:6525162
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:6523848
repl_backlog_histlen:1315
127.0.0.1:7501>
之前的主节点 7501 现已作为从节点工作, 从新任主节点 7501 备份数据.
SpringBoot 集成
SpringBoot 继承 Redis Sentinel Cluster 本身没什么难点, 值得一说的就是 Sentinel 的 requirepass 设置. 这里我直接引入自己的 data-redis-service-spring-boot-starter, 有兴趣的可以看看 SpringBoot 自动配置 (2) - 自己写个 Starter 二次封装 spring-boot-starter-data-redis.
配置文件方面只需要做如下配置:
spring:
redis:
password: redis-cluster-pass
sentinel:
master: redis-5.0.2-master
nodes: 118.24.109.247:7505,118.24.109.247:7506,118.24.109.247:7507
新建测试用例:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisSentinelApplicationTest {
@Autowired
public void setRedisService(RedisService redisService) {
this.redisService = redisService;
}
private RedisService redisService;
@Test
public void testSaving() {
redisService.setValue(
RedisKey.builder().prefix("temp").suffix("foo").build(),
"bar",
300
);
System.out.println("Value set...");
}
}
启动后直接在本地客户端可以看到 4 个 Redis Instance 都有值:
示例源码颇为简单, 代码仓库.
总结
本文介绍了如何一步一步动手搭建 Redis Sentinel 并与 SpringBoot 整合. Sentinel 本质上也是一个独立于 Redis Instance Cluster 的分布式系统. 故障转移本质上也是通过 Sentinel 动态修改 redis.conf 实现的, 可以看到主节点挂掉之后, 从节点的配置文件都有变化.
恩~很有意思.
Reference
- Redis Sentinel Documentation
- Redis Cluster vs. Sentinel
- https://www.cnblogs.com/toutou/p/sentinel.html