楔子
上一节我们介绍了主从复制模式,它是属于 Redis 多机运行的基础,但这种模式本身存在一个致命的问题,当主节点崩溃之后,需要人工干预才能恢复 Redis 的正常使用。
例如,我们有 3 台服务器做了主从复制,一个主服务器 A 和两个从服务器 B、C,当 A 发生故障之后,需要人工把 B 服务器设置为主服务器,同时再去将 C 服务器设置为"主服务器 B "的从服务器、并同步数据。但如果是发生在晚上或者从服务器节点很多的情况下,对于人工来说想要立即实现恢复的难度会很大,所以我们需要一个自动的工具——Redis Sentinel(哨兵模式)来把手动的过程变成自动的,让 Redis 拥有自动容灾恢复(failover)的能力。
哨兵模式如下所示:
Redis Sentinel 的最小分配单位是一主一从。
Redis 哨兵模式(上)
我们说哨兵就是用来监视的,如果没有哨兵的话,那么当master挂了,我们需要slaveof no one
成为master,然后显式地指定其余的机器来跟随这个新的master,而哨兵模式则是自动地执行这一个过程,不需要人为参与。所以就是有个哨兵在监控,如果老大挂了,那么就要从小弟当中选出一个新老大。
那么如何使用哨兵模式呢?
-
1. 首先我们要新建一个文件,这个文件名必须叫做sentinel.conf,放在哪个目录不重要,但是名字不可以错。
-
2. 配置哨兵,填写内容: sentinel monitor <master_name> <master_ip> <mast_port> <quorum>
sentinel monitor my_master 127.0.0.1 6379 1
# master-name 表示给监视的主节点起一个名称,名字随意;
# ip 表示主节点的 IP;
# port 表示主节点的端口;
# quorum 表示确认主节点下线的 Sentinel 数量,如果 quorum 设置为 1 表示只要有一台 Sentinel 判断它下线了,就可以确认它真的下线了。
注意:如果主节点 Redis 服务器有密码,还必须在 sentinel.conf 中添加主节点的密码,不然会导致 Sentinel 不能自动监听到主节点下面的从节点。所以如果 Redis 有密码,sentinel.conf 必须包含以下内容:
sentinel monitor my_master 127.0.0.1 6379 1
sentinel auth-pass my_master <密码> # 当然我们这里没有密码
3. 启动哨兵,不过在启动之前我们还要让6380和6381重新成为6379的从节点,刚才我们给取消了。
redis-sentinel sentinel.conf # redis-sentinel也是Redis提供的,和redis-server在同一个目录下
此时我们就启动了一个哨兵,从上图可以看出 Sentinel 只需配置监听主节点的信息即可,它会自动监听对应的从节点。
启动 Sentinel 集群
上面我们演示了单个 Sentinel 的启动,但生产环境我们不会只启动一台 Sentinel,因为如果启动一台 Sentinel 假如它不幸宕机的话,就不能提供自动容灾的服务了,不符合我们高可用的宗旨,所以我们会在不同的物理机上启动多个 Sentinel 来组成 Sentinel 集群,来保证 Redis 服务的高可用。
启动 Sentinel 集群的方法很简单,和上面启动单台的方式一样,我们只需要把多个 Sentinel 监听到一个主服务器节点,那么多个 Sentinel 就会自动发现彼此,并组成一个 Sentinel 集群。
我们启动第二个 Sentinel 来试一下,当然我们已经启动一个哨兵了,所以直接启动第二个哨兵,会端口冲突,当然可以在sentinel.conf中通过port选项指定一个其它的端口;或者使用一台新的服务器来启动第二个哨兵。
sentinel monitor my_master 127.0.0.1 6379 1
port 26380 # 哨兵默认绑定26379端口,这里我们改成26380启动,不然会冲突
然后你会发现在哨兵的窗口中多了一些输出,其中一条就是告诉我们增加了一个哨兵。
+sentinel sentinel 9412dc8b27bcd14c8056b94b3cd71ac9ce084ffd 127.0.0.1 26380 @ my_master 127.0.0.1 6381
Sentinel 集群示意图如下:
一般情况下 Sentinel 集群的数量取大于 1 的奇数,例如 3、5、7、9,而 quorum 的配置要根据 Sentinel 的数量来发生变化,例如 Sentinel 是 3 台,那么对应的 quorum 最好是 2,如果 Sentinel 是 5 台,那么 quorum 最好是 3,它表示当有 3 台 Sentinel 都确认主节点下线了,就可以确定主节点真的下线了。
当然我们这里只是介绍,所以就不管那么多了,毕竟机器数量有限。
与 quorum 参数相关的有两个概念:主观下线和客观下线。
当 Sentinel 集群中,有一个 Sentinel 认为主服务器已经下线时,它会将这个主服务器标记为主观下线(Subjectively Down,SDOWN),然后询问集群中的其他 Sentinel,是否也认为该服务器已下线,当同意主服务器已下线的 Sentinel 数量达到 quorum 参数所指定的数量时,Sentinel 就会将相应的主服务器标记为客观下线(Objectively down,ODOWN),然后开始对其进行故障转移。
下面来进行一下测试
127.0.0.1:6379> lpush Girl satori koishi mashiro
(integer) 3 # 在6379上创建一个List
127.0.0.1:6379>
127.0.0.1:6380> lrange Girl 0 -1 # 在6380上获取
1) "mashiro"
2) "koishi"
3) "satori"
127.0.0.1:6380>
127.0.0.1:6381> lrange Girl 0 -1 # 在6381上获取
1) "mashiro"
2) "koishi"
3) "satori"
127.0.0.1:6381>
我们看到一切正常,但是天有不测风云,这个时候6379挂掉了(我们手动将6379这个服务杀死)
。
然后哨兵会自动检测,并从剩余的slave中选出master,如图所示:
告诉我们master已经从6379变成了6381,并且6380变成了6381的slave,我们测试一下。
127.0.0.1:6381> lrange Girl 0 -1 # 在6379还没挂掉的时候,同步过来的key还在
1) "mashiro"
2) "koishi"
3) "satori"
127.0.0.1:6381> role # 查看角色,变成了master,并且下面有一个slave
1) "master"
2) (integer) 19740
3) 1) 1) "127.0.0.1"
2) "6380"
3) "19740"
127.0.0.1:6381> set name mea # 然后再设置一个key,看看会不会同步到6380那里去
OK
127.0.0.1:6381>
[root@iZ2ze3ik2oh85c6hanp0hmZ ~]# redis-cli -p 6380
127.0.0.1:6380> get name # 我们退出之后再重新进入,然后依旧可以获取
"mea"
127.0.0.1:6380> role # 查看角色依旧是slave,只不过对应的master从6379变成了6381
1) "slave"
2) "127.0.0.1"
3) (integer) 6381
4) "connected"
5) (integer) 28530
127.0.0.1:6380>
哨兵选取的优先级
我们看到在6379挂掉之后,选择了6381作为master,那么哨兵之间是怎么选择的呢?为什么选择的不是6380呢?其实选取的时候会生成一个随机的ID,然后会选择ID较小的作为master。
但是我们可以通过 redis.conf 中的 replica-priority 选项来设置竞选新主节点的优先级,它的默认值是 100,它的最大值也是 100,这个值越小它的权重就越高,例如从节点 A 的 replica-priority 值为 100,从节点 B 的值为 50,从节点 C 的值为 5,那么在竞选时从节点 C 会作为新的主节点。
新主节点的竞选会排除不符合条件的从节点,然后再剩余的从节点按照优先级来挑选。首先来说,存在以下条件的从节点会被排除:
1. 排除所有已经下线以及长时间没有回复心跳检测的疑似已下线从服务器;
2. 排除所有长时间没有与主服务器通信,数据状态过时的从服务器;
3. 排除所有优先级(replica-priority)为 0 的服务器。
符合条件的从节点竞选顺序:
1. 优先级最高的从节点将会作为新主节点;
2. 优先级相等则判断复制偏移量,偏移量最大的从节点获胜;
3. 如果以上两个条件都相同,选择 Redis 运行时随机生成 ID 最小那个为新的主服务器。
旧主节点恢复上线
我们说master挂了,会选择新的master,但是如果之前那个挂掉的master又回来了,这个时候会是什么情况呢?
[root@iZ2ze3ik2oh85c6hanp0hmZ master]# redis-server redis.conf
[root@iZ2ze3ik2oh85c6hanp0hmZ master]# redis-cli -p 6379
127.0.0.1:6379> role # 重新启动6379,发现自动变成了slave,那么master是谁呢?显然是新master6381
1) "slave"
2) "127.0.0.1"
3) (integer) 6381
4) "connected"
5) (integer) 68632
127.0.0.1:6379>
127.0.0.1:6379> lrange Girl 0 -1
1) "mashiro"
2) "koishi"
3) "satori"
127.0.0.1:6379> get name # 之前设置的key也都会同步过来
"mea"
127.0.0.1:6379>
所以我们看到尽管你之前是master,但是你挂掉了,风水轮流转,即便你回来也只能当slave。
哨兵也输出了,将6379变成了6381的slave。
哨兵工作原理
哨兵的工作原理是这样的,首先每个 Sentinel 会以每秒钟 1 次的频率,向已知的主服务器、从服务器和以及其他 Sentinel 实例,发送一个 PING 命令。如果最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 所配置的值(默认 30s),那么这个实例会被 Sentinel 标记为主观下线。如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有 Sentinel 节点,都要进行判断,是否进入了主观下线状态。
如果有足够数量(quorum 配置值)的 Sentinel 在指定的时间范围内同意这一判断,那么这个主服务器被标记为客观下线。此时所有的 Sentinel 会按照规则协商自动选出新的主节点。
注意:一个有效的 PING 回复可以是:+PONG、-LOADING 或者 -MASTERDOWN。如果返回值非以上三种回复,或者在指定时间内没有回复 PING 命令, 那么 Sentinel 认为服务器返回的回复无效(non-valid)。
Redis 哨兵模式(下)
介绍完了 Redis Sentinel 的搭建和运行原理,下面我们重点来看下 Sentinel 的命令操作和代码实战。
要使用 Sentinel 实现要连接到 Sentinel 服务器,和连接 Redis 服务相同,我们可以使用 redis-cli 来连接 Sentinel,如下命令所示:redis-cli -h <ip> -p <port> -a <password>
,命令是一样的,只不过哨兵占用的端口是26379,不是6379。
[root@iZ2ze3ik2oh85c6hanp0hmZ ~]# redis-cli -p 26379
127.0.0.1:26379> ping
PONG
127.0.0.1:26379>
注意:Sentinel 可以监视多台主节点,而不是只能监视一台服务器。想要监视多台主节点只需要在配置文件中设置多个 sentinel monitor master-name ip port quorum
即可,我们通过 master-name 来区分不同的主节点。
查询所有被监控的主服务器信息
127.0.0.1:26379> sentinel masters
1) 1) "name"
2) "my_master"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6381"
7) "runid"
8) "76ad62079b2056b758de38a79ccdfc775c9774e5"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "381"
19) "last-ping-reply"
20) "381"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "9392"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "1846376"
29) "config-epoch"
30) "1"
31) "num-slaves"
32) "2"
33) "num-other-sentinels"
34) "0"
35) "quorum"
36) "1"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"
因为我们配置的 Sentinel 只监视了一台主服务器,所以只有一台服务器的信息。
查询某个主节点的信息
127.0.0.1:26379> sentinel master my_master # 只有一个master,所以输出和sentinel master是一样的
1) "name"
2) "my_master"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6381"
7) "runid"
8) "76ad62079b2056b758de38a79ccdfc775c9774e5"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "368"
19) "last-ping-reply"
20) "368"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "8140"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "1895176"
29) "config-epoch"
30) "1"
31) "num-slaves"
32) "2"
33) "num-other-sentinels"
34) "0"
35) "quorum"
36) "1"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"
127.0.0.1:26379>
127.0.0.1:26379> sentinel master my_master1 # 查询一个不存在的master会报错
(error) ERR No such master with that name
127.0.0.1:26379>
查看某个主节点的 IP 和端口
127.0.0.1:26379> sentinel get-master-addr-by-name my_master
1) "127.0.0.1"
2) "6381"
127.0.0.1:26379>
查询从节点的信息
127.0.0.1:26379> sentinel slaves my_master # 在Redis5.0之后,可以将slaves换成replicas
1) 1) "name"
2) "127.0.0.1:6379"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6379"
7) "runid"
8) "8ff4549036fece921331c8a35ac9c4704342d58a"
9) "flags"
10) "slave"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "130"
19) "last-ping-reply"
20) "130"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "1330"
25) "role-reported"
26) "slave"
27) "role-reported-time"
28) "975162"
29) "master-link-down-time"
30) "0"
31) "master-link-status"
32) "ok"
33) "master-host"
34) "127.0.0.1"
35) "master-port"
36) "6381"
37) "slave-priority"
38) "100"
39) "slave-repl-offset"
40) "134079"
2) 1) "name"
2) "127.0.0.1:6380"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6380"
7) "runid"
8) "bcda9f96d6228b88cf356b967c0e8dd7286fd32b"
9) "flags"
10) "slave"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "130"
19) "last-ping-reply"
20) "130"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "4991"
25) "role-reported"
26) "slave"
27) "role-reported-time"
28) "2012601"
29) "master-link-down-time"
30) "0"
31) "master-link-status"
32) "ok"
33) "master-host"
34) "127.0.0.1"
35) "master-port"
36) "6381"
37) "slave-priority"
38) "100"
39) "slave-repl-offset"
40) "133811"
127.0.0.1:26379>
查询 Sentinel 集群中的其他 Sentinel 信息
127.0.0.1:26379> sentinel sentinels my_master
1) 1) "name"
2) "9412dc8b27bcd14c8056b94b3cd71ac9ce084ffd"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "26380" # 端口是26380,显示了另一个哨兵,由于显示的是其它哨兵,所以26379本身这个哨兵没有显示
7) "runid"
8) "9412dc8b27bcd14c8056b94b3cd71ac9ce084ffd"
9) "flags"
10) "sentinel"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "818"
19) "last-ping-reply"
20) "818"
21) "down-after-milliseconds"
22) "30000"
23) "last-hello-message"
24) "477"
25) "voted-leader"
26) "?"
27) "voted-leader-epoch"
28) "0"
127.0.0.1:26379>
检查可用 Sentinel 的数量
127.0.0.1:26379> sentinel ckquorum my_master
OK 2 usable Sentinels. Quorum and failover authorization can be reached
127.0.0.1:26379>
强制故障转移
127.0.0.1:26379> sentinel failover my_master
OK
127.0.0.1:26379>
在 Redis 2.8.4 之前如果需要修改 Sentinel 的配置文件,例如添加或删除一个监视主节点,需要先停止 Sentinel 服务,再找到配置文件修改之后,重新启动 Sentinel 才行,这样就给我们带来了很多的不便,尤其是生产环境的 Sentinel,正常情况下如果是非致命问题我们是不能手动停止服务的,幸运的是 Redis 2.8.4 之后,我们可以不停机在线修改配置文件了,修改命令有以下几个。
增加监视主节点
可以使用 sentinel monitor <master_name> IP Port Quorum
命令来添加监视主节点,返回OK 表示添加监视主节点成功。
移除主节点的监视
使用 sentinel remove <master_name>
命令来实现移除主节点的监视,返回OK 表示移除成功。
修改 quorum 参数
使用 sentinel set <master_name> quorum n
来修改 quorum 参数。
quorum 参数用来表示确认主节点下线的 Sentinel 数量,如果 quorum 设置为 1 表示只要有一台 Sentinel 确认主观下线后,这个主节点就客观(真正地)下线了。
以上所有对配置的修改,都会自动被刷新到物理配置文件 sentinel.conf 中。
小结
Redis的哨兵模式在工作中是非常常用的,为了防止意外故障,那么我们都会采用哨兵模式进行自动监测、实现故障转移。
哨兵模式就说到这里,下一篇博客会介绍Redis的一些常见问题,比如缓存失效、缓存雪崩等等。另外,Redis除了主从复制、哨兵模式之外,还可以搭建集群,多个master(slaves)
组合成一个集群,主要是如果只有一个master,虽然读操作被slave节点分担了,但是当写操作非常频繁的时候,master节点的压力也会比较大,所以我们会将多个master组成集群。不过关于集群这里就不说了,可以参考一下官网,因为Redis集群我几乎没什么用过,而且所在公司的业务还达不到需要Redis集群的地步。