概述
Redis Sentinel,即Redis哨兵,在Redis 2.8版本开始引入。它是Redis高可用的实现方案之一。Sentinel是一个管理多个Redis实例的工具,它的核心功能是可以实现对Redis的监控、通知、自动故障转移。
- 监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
- 自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
- 配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
- 通知(Notification):哨兵可以将故障转移的结果发送给客户端。
其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移;而配置提供者和通知功能,则需要在与客户端的交互中才能体现。
这里的客户端概率和前面文章的客户端有所区别:在前面的文章中,只要通过API访问redis服务器,都会称作客户端,包括redis-cli、Java客户端Jedis等;为了便于区分说明,本文中的客户端并不包括redis-cli,而是比redis-cli更加复杂:redis-cli使用的是redis提供的底层接口,而客户端则对这些接口、功能进行了封装,以便充分利用哨兵的配置提供者和通知功能。
架构
典型的哨兵架构图如下所示:
它由两部分组成,哨兵节点和数据节点。其中哨兵Sentinel 节点负责监控集群中所有的数据节点包括主、从Redis,当发现主故障时,Sentinel会在所有的从中选一个成为新的主。并且会把其余的从变为新主的从。同时那台有问题的旧主也会变为新主的从,也就是说当旧的主即使恢复时,并不会恢复原来的主身份,而是作为新主的一个从。
在Redis高可用架构中,Sentinel往往不是只有一个,而是有3个或者以上。目的是为了让其更加可靠,毕竟主和从切换角色这个过程还是蛮复杂的。
相关概念
主观失效
SDOWN(subjectively down),直接翻译的为"主观"失效,即当前sentinel实例认为某个redis服务为"不可用"状态.
客观失效
ODOWN(objectively down),直接翻译为"客观"失效,即多个sentinel实例都认为master处于"SDOWN"状态,那么此时master将处于ODOWN,ODOWN可以简单理解为master已经被集群确定为"不可用",将会开启failover。
部署
环境说明
主机名称 | IP地址 | redis版本和角色说明 |
---|---|---|
k8s-m1 | 192.168.2.140:6379 | redis 6.0.6(主) |
k8s-m2 | 192.168.2.141:6379 | redis 6.0.6(从) |
k8s-m3 | 192.168.2.142:6379 | redis 6.0.6(从) |
k8s-m1 | 192.168.2.140:26379 | Sentinel01 |
k8s-m2 | 192.168.2.141:26379 | Sentinel02 |
k8s-m3 | 192.168.2.142:26379 | Sentinel03 |
部署主从节点
哨兵系统中的主从节点,与普通的主从节点配置是一样的,并不需要做任何额外配置。具体方法可以参考之前文章的部署步骤。两个从节点的配置除了bind其余完全一样。主从部署完后正常的情况大致如下:
bash
192.168.2.140:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.2.141,port=6379,state=online,offset=82978,lag=0
slave1:ip=192.168.2.142,port=6379,state=online,offset=82978,lag=0
master_replid:8e2309122a5b93327f8469e1b7c1be17c0f23499
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:82978
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:82978
192.168.2.140:6379>
从返回结果可以看到,当前节点192.168.2.140为master,连接它的有两个slave节点。
部署哨兵节点
哨兵节点本质上是特殊的Redis节点。3个哨兵节点的配置除了绑定的IP地址几乎是完全一样的,下面以某个节点为例介绍节点的配置和启动方式;以下是配置的简化版,实际使用中可以根据使用情况进行修改。
bash
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/usr/local/redis/sentinel.log"
sentinel monitor mymaster 192.168.2.142 6379 2
说明,sentinel monitor mymaster 192.168.2.142 6379 2 配置的含义是:该哨兵节点监控192.168.2.142:6379这个主节点,该主节点的名称是mymaster,最后的2的含义与主节点的故障判定有关:至少需要2个哨兵节点同意,才能判定主节点故障并进行故障转移。这一行是会根据集群的实际情况进行自动变动的。
哨兵节点的启动有两种方式,二者作用是完全相同的:
bash
redis-sentinel sentinel.conf
redis-server sentinel.conf --sentinel
按照上述方式配置和启动之后,整个哨兵系统就启动完毕了。可以通过redis-cli连接哨兵节点进行验证,如下,注意要指定端口。
bash
[root@k8s-m1 redis]# ./src/redis-cli -p 26379
127.0.0.1:26379> INFO sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.2.141:6379,slaves=2,sentinels=4
127.0.0.1:26379>
可以看出26379哨兵节点已经在监控mymaster主节点(即192.168.2.141:6379),并发现了其2个从节点和另外2个哨兵节点。
当所有的数据节点和哨兵节点都正常启动稳定后,此时查看哨兵节点的配置文件,会发现一些变化,以其中一个节点为例::
bash
[root@k8s-m1 redis]# tail sentinel.conf
# SENTINEL rename-command mymaster CONFIG CONFIG
# Generated by CONFIG REWRITE
protected-mode no
user default on nopass ~* +@all
sentinel known-replica mymaster 192.168.2.140 6379
sentinel known-replica mymaster 192.168.2.142 6379
sentinel known-sentinel mymaster 192.168.2.142 26379 3e4ccc63758d9bb4f087943a4094bc103a5c0b36
sentinel known-sentinel mymaster 192.168.2.141 0 f0d97ebd0d517c3ac74059011102d7db0e4723db
sentinel known-sentinel mymaster 192.168.2.142 26379 89cdab64fa10d20d827c3e4be7d7f44932e6e5ab
sentinel current-epoch 4
说明,known-replica 和known-sentinel 显示哨兵已经发现了从节点和其他哨兵;带有epoch的参数与配置纪元有关(配置纪元是一个从0开始的计数器,每进行一次领导者哨兵选举,都会+1;领导者哨兵选举是故障转移阶段的一个操作,在后文原理部分会介绍)。
哨兵的相关操作
bash
127.0.0.1:26379> sentinel master mymaster ##查看主节点的状态信息
1) "name"
2) "mymaster"
3) "ip"
4) "192.168.2.141"
5) "port"
6) "6379"
7) "runid"
8) "b2741413c7eddeda239333da172675f8d0c49969"
9) "flags"
10) "master"
127.0.0.1:26379> sentinel slaves mymaster ##查看从节点的状态信息,实际是可以看到2个从节点的信息,此处省略了一部分
1) 1) "name"
2) "192.168.2.142:6379"
3) "ip"
4) "192.168.2.142"
5) "port"
6) "6379"
7) "runid"
8) "6991f330bf5a2410b0718327d92456a329f8c912"
9) "flags"
10) "slave"
......
127.0.0.1:26379> sentinel sentinels mymaster ##查看其它sentinel信息
1) 1) "name"
2) "89cdab64fa10d20d827c3e4be7d7f44932e6e5ab"
3) "ip"
4) "192.168.2.142"
5) "port"
6) "26379"
7) "runid"
8) "89cdab64fa10d20d827c3e4be7d7f44932e6e5ab"
9) "flags"
10) "sentinel"
......
127.0.0.1:26379>
演示故障转移
下面将演示当主节点发生故障时,哨兵的监控和自动故障转移功能。
(1)首先,使用systemctl stop redis
或者kill 命令停止主节点:
bash
[root@k8s-m2 redis]# ps aux|grep redis
root 1144 0.3 0.0 257312 4448 ? Ssl Mar18 49:02 /usr/local/redis/src/redis-server 192.168.2.141:6379
root 17653 0.6 0.0 162684 3332 ? Ssl Mar27 6:39 ./src/redis-sentinel *:26379 [sentinel]
root 31147 0.0 0.0 112816 972 pts/1 S+ 10:38 0:00 grep --color=auto redis
[root@k8s-m2 redis]# systemctl stop redis
[root@k8s-m2 redis]# ps aux|grep redis
root 17653 0.6 0.0 162684 3332 ? Ssl Mar27 6:39 ./src/redis-sentinel *:26379 [sentinel]
root 32017 0.0 0.0 112816 972 pts/1 S+ 10:39 0:00 grep --color=auto redis
(2)如果此时立即在哨兵节点中使用info Sentinel命令查看,会发现主节点还没有切换过来,因为哨兵发现主节点故障并转移,需要一段时间。
bash
127.0.0.1:26379> INFO sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.2.141:6379,slaves=2,sentinels=4
(3)一段时间以后,再次在哨兵节点中执行info Sentinel查看,发现主节点已经从192.168.2.141:6379切换成192.168.2.140:6379节点。
bash
127.0.0.1:26379> INFO sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.2.140:6379,slaves=2,sentinels=4
但是同时可以发现,哨兵节点认为新的主节点仍然有2个从节点,这是因为哨兵在切换成主节点的同时,将原来的主节点置为其从节点;虽然原来的主节点已经挂掉,但是由于哨兵并不会对其进行客观下线(其含义将在原理部分介绍),因此认为该从节点一直存在。当原来的主节点重新启动后,会自动变成现有主节点的从节点。下面验证一下。
(4)重启节点192.168.2.141上的redis:可以看到节点192.168.2.141成为了节点192.168.2.140的从节点(online)。注意要去当前的主节点上查看。
bash
192.168.2.140:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.2.142,port=6379,state=online,offset=140437186,lag=0
slave1:ip=192.168.2.141,port=6379,state=online,offset=140437186,lag=0
master_replid:3da2ea6bc0d4fdeb68065ca00d87d05f0217e5ef
master_replid2:6c86696332d33fe60ba1ea097c5ed1d4cab6987e
master_repl_offset:140437327
second_repl_offset:140359628
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:139679072
repl_backlog_histlen:758256
192.168.2.140:6379>
(5)在故障转移阶段,哨兵和主从两类节点的配置文件都会被自动改写。
对于主从节点,主要是slaveof配置的变化:新的主节点没有了slaveof配置,其从节点则slaveof新的主节点。如下:k8s-m2(192.168.2.141)为从节点
bash
[root@k8s-m2 redis]# tail /etc/redis/redis.conf
# to suppress
#
# ignore-warnings ARM64-COW-BUG
# Generated by CONFIG REWRITE
save 3600 1
save 300 100
save 60 10000
user default on sanitize-payload #8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 ~* &* +@all
requirepass "123456"
replicaof 192.168.2.140 6379
对于哨兵节点,除了主从节点信息的变化,纪元(epoch)也会变化,下图中可以看到纪元相关的参数都+1了。
root@k8s-m2 redis\]# tail sentinel.conf
```bash
# SENTINEL rename-command mymaster CONFIG CONFIG
# Generated by CONFIG REWRITE
protected-mode no
user default on nopass sanitize-payload ~* &* +@all
sentinel known-replica mymaster 192.168.2.141 6379
sentinel known-replica mymaster 192.168.2.142 6379
sentinel known-sentinel mymaster 192.168.2.141 26379 f0d97ebd0d517c3ac74059011102d7db0e4723db
sentinel known-sentinel mymaster 192.168.2.141 26379 3e4ccc63758d9bb4f087943a4094bc103a5c0b36
sentinel current-epoch 6
sentinel known-sentinel mymaster 192.168.2.142 26379 89cdab64fa10d20d827c3e4be7d7f44932e6e5ab
```
### 总结
哨兵系统的搭建过程,有几点需要注意:
(1)哨兵系统中的主从节点,与普通的主从节点并没有什么区别,故障发现和转移是由哨兵来控制和完成的。
(2)哨兵节点本质上是redis节点。
(3)每个哨兵节点,只需要配置监控主节点,便可以自动发现其他的哨兵节点和从节点。
(4)在哨兵节点启动和故障转移阶段,各个节点的配置文件会被重写(config rewrite)。
(5)上面的示例中,一个哨兵只监控了一个主节点;实际上,一个哨兵可以监控多个主节点,通过配置多条sentinel monitor即可实现。
## 客户端访问哨兵系统
上一小节演示了哨兵的两大作用:监控和自动故障转移,本小节则结合客户端演示哨兵的另外两个作用:配置提供者和通知。
### 代码示例
先以Java客户端Jedis为例,下面代码可以连接我们的哨兵系统,并进行各种读写操作(代码中只演示如何连接哨兵,异常处理、资源关闭等未考虑)。
```bash
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.exceptions.JedisException;
import java.util.HashSet;
import java.util.Set;
public class RedisSentinelExample {
public static void main(String[] args) {
// Redis Sentinel 配置
String masterName = "mymaster";
Set