redis 指南(7): 谋权篡位 | 哨兵监视下的权力交替

437 阅读12分钟

前言

主从复制的模式极大的增强了单机 redis 的高可用,但是它有个非常大的缺点,如果主机宕机之后,那么整个 redis 的主从模式就无法写入数据了,从节点无法恢复成主节点,而老大却迟迟醒不过来,那么 这套 redis 方案是不可行的,因此,我们需要找到一种方式,让 作为从机的小弟具备某种野心,在老大死亡之后,小弟也能振臂一挥,篡权夺位,成为新的领袖,带领剩下的小弟,继续帮我们工作,这种新的模式就是我们今天要讲的---- 哨兵机制。

一、概述

哨兵模式是一种特殊的模式,它是作为一个独立的进程存在的,独立进程是通过哨兵发送命令,从而监测运行的 redis 服务,例如下面这种:

image-20210903163613259

这里的哨兵起着两个作用:

  • 通过发送命令,让 redis 服务器返回监控其运行状态,包括主服务器 和从服务器
  • 当哨兵检测到 master 宕机,会自动切换 某个 slave 为 master,然后通过发布订阅模式,通知其他的从服务器,修改配置文件,让他们切换主机。
  • 当 salve 宕机后,再它恢复 的时候能够自动加入到主从复制架构中,进行数据同步工作

但是,一台哨兵也是非常容易出问题的,所以哨兵也需要搭建个集群,哨兵除了检测 redis 服务器之外,还会相互监控,如下

image-20210903164044675

哨兵集群 类似于 zookeeper 集群,它是 redis 集群高可用的心脏,当主节点宕机之后,哨兵会自动选择一个最优的从节点切换成主节点。

客户端来连接集群的时候,也会首先取连接 哨兵,通过哨兵来查询主节点的地址,然后再连接主节点进行数据交互。

当主节点发生故障,客户端会重新向哨兵要地址,然后哨兵把最新的主节点地址告诉客户端,即可完成主节点的切换。

二、哨兵搭建

一主二从三哨兵架构部署。

服务ip角色端口
192.168.81.101redis master6379
192.168.81.102redis slave6379
192.168.81.103redis slave6379
192.168.81.101Sentinel26379
192.168.81.102Sentinel26379
192.168.81.103Sentinel26379

2.1 哨兵搭建

1、首先配置 Redis 的主服务器,修改 redis 目录下的 sentinel.conf 文件如下,三台服务器的 sentinel 都一致。

port 26379                                          # 默认端口
daemonize yes                                       # 设置后台启动
protected-mode no                                   # 关闭保护模式,可以外部访问
sentinel monitor mymaster 192.168.81.101 6379 2     # 指定主机 ip 端口,当有2台或以上哨兵认为主机挂了,则对主机进行容灾切换。
sentinel auth-pass mymaster redis@admin2021         # redis 开启了密码,则哨兵也要配置密码
logfile "/opt/software/redis6/log/sentinel.log"     # 日志

2、创建日志文件,好分析日志的打印过程

[root@localhost log]# pwd
/opt/software/redis6/log
[root@localhost log]# ls
sentinel.log

3、然后每台先启动 redis-server ,再启动哨兵,注意,redis-cli 的时候要指定 -p 端口访问,不然获取不到 sentinel 的信息。

[root@localhost src]# ./redis-server ../redis.conf 
[root@localhost src]# ./redis-sentinel ../sentinel.conf
[root@localhost 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.81.101:6379,slaves=2,sentinels=3

2.2 容灾测试

1)先杀掉 101 主的 redis 服务

root      84915      1  0 15:40 ?        00:00:01 ./redis-server 0.0.0.0:6379
root     100784      1  0 15:46 ?        00:00:00 ./redis-sentinel 0.0.0.0:26379 [sentinel]
root     110715 109812  0 15:51 pts/2    00:00:00 grep --color=auto redis
[root@localhost src]# kill -9 84915

2)哨兵会在30秒内开始进行投票选举主机

image-20210903155726193

3)此时,由102 节点变成主节点,然后 103 为 102 的从节点

102 的节点信息:

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.81.103,port=6379,state=online,offset=142266,lag=0
master_replid:ef5f206a2ee390bde419850907e9b99d9ad580da
master_replid2:1e22d835260f24326bd0b4bffa0f9ab59863917b
master_repl_offset:142266
second_repl_offset:88546
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:24157
repl_backlog_histlen:118110

再次查看 redis.conf 文件,发现在最后一行加上了这个:

image-20210903172427287

103 的信息:

127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.81.102
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:129312
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:ef5f206a2ee390bde419850907e9b99d9ad580da
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:129312
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:91143
repl_backlog_histlen:38170

4)再次启动 101 ,发现它昔日荣光不再,变成了 103 的从节点,而谋权者 103 已得逞。

127.0.0.1:6379>  info replication
# Replication
role:slave
master_host:192.168.81.103
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1630656276
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:ea4d4811f0c6133bee4896c0287cadba11c53712
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

三、原理篇

3.3 工作原理

哨兵系统的作用用来管理多个 Redis 服务器,该系统执行以下三个任务:

  • 1、监控:哨兵会不断地检查你的 Master 和 slave 是否运行正常。
  • 2、提醒:当被监控的某个 Redis 出现问题时,哨兵可以通过 API 向 管理员或者其他应用程序发送通知。
  • 3、自动故障转移:当一个 master 不能正常工作时,哨兵会开始一次自动故障迁移操作,它会将失效 master 的其中一个 slave 升级为新的 master,并让某个 slave 成为新的 Master,使得集群可以使用新的 master 代替失效的master。
3.3.1 监控

sentinel 会每秒一次的频率 与之前创建了命令连接的实例发送 PING,包括主服务器、从服务器和 sentinel 实例,以此来判断当前实例的状态。

down-after-milliseconds时间内PING连接无效,则将该实例视为主观下线。之后该sentinel会向其他监控同一主服务器的sentinel实例询问是否也将该服务器视为主观下线状态,当超过某quorum后将其视为客观下线状态。

当一个主服务器被某sentinel视为客观下线状态后,该sentinel会与其他sentinel协商选出零头sentinel进行故障转移工作。每个发现主服务器进入客观下线的sentinel都可以要求其他sentinel选自己为领头sentinel,选举是先到先得同时每个sentinel每次选举都会自增配置纪元,每个纪元中只会选择一个领头sentinel。如果所有超过一半的sentinel选举某sentinel领头sentinel。之后该sentinel进行故障转移操作。

如果一个Sentinel为了指定的主服务器故障转移而投票给另一个Sentinel,将会等待一段时间后试图再次故障转移这台主服务器。如果该次失败另一个将尝试,Redis Sentinel保证第一个活性(liveness)属性,如果大多数Sentinel能够对话,如果主服务器下线,最后只会有一个被授权来故障转移。 同时Redis Sentinel也保证安全(safety)属性,每个Sentinel将会使用不同的配置纪元来故障转移同一台主服务器。

3.3.2 故障转移

1、首先是从主服务器的从服务器中选出一个从服务器作为新的主服务器。选点的依据依次是:网络连接正常->5秒内回复过INFO命令->10*down-after-milliseconds内与主连接过的->从服务器优先级->复制偏移量->运行id较小的。

2、选出之后通过slaveif no ont将该从服务器升为新主服务器,并命令让其他从服务器复制该主服务器。

3、最后当旧主重新连接后将其变为新主的从服务器。注意如果客户端与就主服务器分隔在一起,写入的数据在恢复后由于旧主会复制新主的数据会造成数据丢失。

4、故障转移成功后会通过发布订阅连接广播新的配置信息,其他sentinel收到后依据配置来更新主服务器信息。

3.2 消息丢失

redis 主从采用异步复制来保证了数据的最终一致性,这也就意味着当主节点挂掉之后,从节点可能没有收到全部的同步消息,会有部分消息丢失的可能。如果主从延迟特别大,那么这丢失的数据量其实也不少。遗憾的是哨兵模式无法保证消息完全不丢失,但可以通过一些配置来让其少丢失数据。它有两个选项可以限制主从延迟过大。

min-slaves-to-write 1
min-slaves-max-lag 10

第一个参数表示主节点必须有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性。

何为正常复制,何为异常复制,这是由第二个参数决定的。它表示如果 10 s 内没有收到 从节点的反馈,就意味着从节点同步不正常。

3.3 配置详解

# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 ip port 
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
 
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
 
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
​
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: 
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。  
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh