【进阶篇】Redis深入理解与实践指南(十一)之Redis高可用之哨兵模式

409 阅读18分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

概述

哨兵(Sentienl)模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。

原理

底层原理是心跳检测(keep-alived),是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

应用场景

当主服务器宕机后,需要手动把一台从服务器切换为主服务器;这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,哨兵模式能够自动切换主从节点。因此,更多时候,我们优先考虑哨兵模式

img

Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。

主从切换的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从节点切换为主节点。

这里的哨兵有两个作用

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式

Redis配置哨兵模式

配置3个哨兵和1主2从的Redis服务器来演示这个过程。

服务类型是否是主服务器IP地址端口
Redis192.168.11.1286379
Redis192.168.11.1296379
Redis192.168.11.1306379
Sentinel-192.168.11.12826379
Sentinel-192.168.11.12926379
Sentinel-192.168.11.13026379

img

除了监控各个Redis服务器之外,哨兵之间也会互相监控!

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover(故障转移)过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。主从切换要经历主观下线 => 客观下线的过程,即发现问题到解决问题,最终完成是==以客观下线为准==。

手动切换主从节点

如果主机断开了连接,我们可以使用slaveof no one让自己变成主机,其他节点就可以手动连接到最新的这个主节点!

127.0.0.1:6379> auth 123456
127.0.0.1:6379> slaveof no one  # 将从机切换为主机,单向的
OK

选举算法

选举算法(Raft协议,Term(任期),RPC

Raft是用于实施分布式共识的协议,其中节点有三种状态:领导(Leader)、跟随(Follow)和候选(candidate)。所有的节点都以跟随者(Folllow)状态开始,没有收到的数据(即主节点宕机后)的跟随(从)节点将成为候选节点;然后候选人向其他节点请求投票,投票多的候选节点将成为领导人(Leader/Master),这个过程称为领导人选举。每一个任期(Term) 的开始都是一个选举过程,如果一个候选人赢得了选举,它就会在该任期的剩余时间担任领导人;如果没有(从节点票数相同)选出领导人,那么马上开始下一个任期。Term是为了**==解决数据同步问题==Raft算法保证在给定的一个任期内最多是有==一个==领导人。后面Redis Cluster**(集群)使用了类似于Raft算法term(任期)的概念,称为epoch(==纪元==),用来给时间增加版本号(==自增ID==)。选举结束后,新主节点最后以广播的方式传播通知其他节点。那么,是以时间?权重?还是数据量为选举依据?如果选举时候选人的宕机时间一致呢?如果数据量也相同呢?

到底选举是以什么标准进行选举呢?即选票是什么

选举Leader的四大因素

1.跟master断开连接的时长

  首先,如果一个slave跟master断开连接已经超过了down-after-milliseconds的10倍,外加master宕机的时长,那么slave就被认为不适合选举为master。

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

2.slave优先级

  按照slave优先级进行排序,slave priority越低,优先级就越高,默认100,值最低成为主

3.复制offset

  如果slave priority相同,那么看replica offset(复制偏移量),哪个slave复制了越多的数据,offset越靠后,优先级就越高

4.run id

  如果上面两个条件都相同,那么选择一个run id(runid可以通过info命令查看,redis每次重启runid都会变动,是系统根据重启时间赋予的)比较小的那个slave;值越小,说明重启时间越靠前

Raft算法就是通过以上4点因素进行协商考虑进而选举出主节点(Master/Leader)的一种算法。

 哨兵完成切换之后,会在自己本地更新生成最新的master配置,然后同步给其他的哨兵,就是通过之前说的pub/sub**==发布/订阅==消息机制(具体实现:选举时将节点信息添加到日志条目中,选为Leader后提交给所有存活节点,完成同步,该过程称为==日志复制==**)。这里之前的version号就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成一次新的切换之后,新的master配置是跟着新的==version==号的。其他的哨兵都是根据版本号的大小来更新自己的master配置的

使用哨兵

测试!

我们目前的状态还是一主二从!

1、配置哨兵配置文件 sentinel.conf

# 进入/user/local/bin/redis/redis-conf目录
cd redis-conf
​
# 创建并编辑sentinel配置文件,这里的1是选举的优先级,类似于思科的STP(Spaning Tree Protocol,生成树协议)用到的决策算法
vim sentinel.conf
​
# sentinel monitor 被监控的主机别名 host port priority(优先级,即需要主观判断挂了的节点数)
sentinel monitor master6379 127.0.0.1 6379 1    
# 主密码,不设置的话不能动态切换(因为默认是将保护模式关闭,密码为空,设置了密码则需要配置)
sentinel auth-pass master6379 123456

后面的这个数字1(为优先级),代表主机挂了,slave投票看让谁接替主机;值越小,票数越多。票数最多的,就会成为主机!设置密码到哨兵配置文件中后,原来的所有节点均需要重新确定密码!

2、启动哨兵

[root@iZwz97gjh27h6busjtqk4jZ redis]#  /usr/local/bin/redis/redis-sentinel redis-conf/sentinel.conf     # 使用配置文件启动哨兵
165849:X 03 Jan 2021 05:17:58.167 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
165849:X 03 Jan 2021 05:17:58.167 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=165849, just started
165849:X 03 Jan 2021 05:17:58.167 # Configuration loaded
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.9 (00000000/0) 64 bit
  .-`` .-```.  ```/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 165849
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               
​
165849:X 03 Jan 2021 05:17:58.169 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
165849:X 03 Jan 2021 05:17:58.169 # Sentinel ID is 2c3f52bc006fcaabb305e3bd766bf242ca9a8c1c
165849:X 03 Jan 2021 05:17:58.169 # +monitor master master6379 127.0.0.1 6379 quorum 1
165849:X 03 Jan 2021 05:17:58.171 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ master6379 127.0.0.1 6379
165849:X 03 Jan 2021 05:17:58.175 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ master6379 127.0.0.1 6379

注意事项

注意:如果主从服务器设置密码,需主从服务器密码保持一致,否则哨兵机制会失败!而且哨兵(Sentinel)要在主机存活时开启,否则会报Next failover delay: I will not start a failover before不会开启故障转移的错误。并且要是在监视当前主机,如果监控的端口不是主机也会报错。

  1. 在主从搭建好的基础上配置哨兵模式,无需修改主从配置的redis.conf,因为之前已配好

  2. 修改哨兵配置文件sentinel.conf

    sentinel monitor mymaster 192.168.72.101 6379 1  # -主地址|主端口|参与选举个数配置基数3-2
    ​
    sentinel auth-pass mymaster 12345678   # -主密码,不设置的话不能动态切换
    
  3. 原来鉴权过的哨兵中还需要==重新确认密码==(只有==选举成功==的主机与宕机重启的原主机需要,即主从切换的服务器需要)

    127.0.0.1:6381> auth 123456
    OK
    
  4. 启动顺序(推荐)

    主服务->从服务->哨兵监听

哨兵开启主要注意 主机存活时开启主从节点密码一致切换主机后连接需重新确认密码 这3个问题即可.

测试结果

# 哨兵监控日志
165849:X 03 Jan 2021 05:19:34.961 # +sdown master master6379 127.0.0.1 6379     # 主观宕机
165849:X 03 Jan 2021 05:19:34.961 # +odown master master6379 127.0.0.1 6379 #quorum 1/1  # 客观宕机
165849:X 03 Jan 2021 05:19:34.961 # +new-epoch 2    # 开启两个新纪元(选举周期)
165849:X 03 Jan 2021 05:19:34.961 # +try-failover master master6379 127.0.0.1 6379  # 开始故障迁移
165849:X 03 Jan 2021 05:19:34.965 # +vote-for-leader 2c3f52bc006fcaabb305e3bd766bf242ca9a8c1c 2     # 开始投票
165849:X 03 Jan 2021 05:19:34.965 # +elected-leader master master6379 127.0.0.1 6379 # 选举主节点
165849:X 03 Jan 2021 05:19:34.965 # +failover-state-select-slave master master6379 127.0.0.1 6379   # 故障迁移状态-选举主节点状态
165849:X 03 Jan 2021 05:19:35.031 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ master6379 127.0.0.1 6379     # 已选好从节点6381为新的主节点
165849:X 03 Jan 2021 05:19:35.031 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ master6379 127.0.0.1 6379  # 与原主节点进行交接,即两台服务器主从切换
165849:X 03 Jan 2021 05:19:35.098 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ master6379 127.0.0.1 6379
165849:X 03 Jan 2021 05:19:35.501 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ master6379 127.0.0.1 6379
165849:X 03 Jan 2021 05:19:35.501 # +failover-state-reconf-slaves master master6379 127.0.0.1 6379
165849:X 03 Jan 2021 05:19:35.587 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ master6379 127.0.0.1 6379
165849:X 03 Jan 2021 05:19:36.535 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ master6379 127.0.0.1 6379
165849:X 03 Jan 2021 05:19:36.535 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ master6379 127.0.0.1 6379
165849:X 03 Jan 2021 05:19:36.588 # +failover-end master master6379 127.0.0.1 6379  # 故障迁移完成
165849:X 03 Jan 2021 05:19:36.588 # +switch-master master6379 127.0.0.1 6379 127.0.0.1 6381     # 主节点切换完成
165849:X 03 Jan 2021 05:19:36.588 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ master6379 127.0.0.1 6381
165849:X 03 Jan 2021 05:19:36.588 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ master6379 127.0.0.1 6381
165849:X 03 Jan 2021 05:20:06.596 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ master6379 127.0.0.1 6381  # 最后再检测一次原主节点是否存活(主观宕机检测)

165849:X 03 Jan 2021 05:43:50.518 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ master6379 127.0.0.1 6381
165849:X 03 Jan 2021 05:44:00.504 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ master6379 127.0.0.1 6381   # 重新启动宕机的原主节点,哨兵会将原主节点切换为从节点

通过配置哨兵模式,将主节点宕机,一段时间后哨兵将剩下的从节点中选举出了一个从节点(6381服务器)作为主节点(Master)从而实现了主从节点的动态切换!

如果Master节点断开了,这个时候就会从从机中随机选择一个服务器!(Raft协议选举)

image-20210103072530395

如果主机此时回来了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则!

哨兵模式

哨兵优缺点

优点

  • 哨兵集群,基于主从模式的,所有主从的优点,哨兵模式都有
  • 主从可以自动切换,故障可以转移,系统的可用性就会更高
  • 哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点

  • redis 较难支持在线扩容,集群容量一旦到达上限时,在线扩容就会变得十分困难!
  • 实现哨兵模式的配置其实是很麻烦的,里面有很多选择!

哨兵模式全部配置

哨兵模式的全部配置

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

哨兵模式常用配置

简化配置

# 基础配置
protected-mode no       # 关闭保护模式                                  
port 26479              # 端口                                                         
daemonize yes           # 使用后台模式启动                                                 
pidfile "/var/run/redis-sentinel_26479.pid"            # 进程id文件                          logfile "/usr/local/redis/sentinel/sentinel_26479.log" # 日志文件                            dir "/usr/local/redis/sentinel"               # 工作目录# 核心配置
​
1、 sentinel monitor <master-name> <ip> <port> <quorum>
# master-name:redis主节点昵称。
# ip:redis主机ip。
# port:redis主机端口。
# quorum:哨兵判断主节点是否发生故障的票数。如果设置为2,表示2个哨兵节点认为主节点发生了故障,一般设置为:哨兵节点数/2+1。
​
2、sentinel down-after-milliseconds <master-name> <times>
# 哨兵会定期的向redis节点发送ping命令来判断redis是否可达,若超过指定的times毫秒内还未得到pong回复,则判读该redis不可达。
​
3、sentinel parallel-syncs <master-name> <nums>
# 当redis主节点挂了后,哨兵会选出新的master,此时,剩余的slave会向新的master发起同步数据,这个设置表示允许并行同步的slave个数。
​
4、sentinel failover-timeout <master-name>  <times>
# 进行故障转移时,如果超过设置的times毫秒(默认30s),表示故障转移失败。
​
5、sentinel auth-pass <master-name> <password>
# 如果redis主节点设置了密码,则需要进行这个配置。# 配置redis主从复制、读写分离
# 配置思路:master配置文件不需要动,修改slave的配置文件。
​
1、replicaof <masterip> <masterport>
# 定义从节点连接主节点网络地址
​
2、masterauth <master-password>
# 如果master配置有密码,则需要配置这一行
​
3、replica-read-only yes    
#表示slave中的数据是只读的(不用配置,默认为只读)

SpringBoot中使用哨兵模式

1.添加yml配置文件

spring:
  redis:
    database: 0
    password: 12345678
    sentinel:
      master: mymaster
      nodes: 192.168.43.243:26379,192.168.0.1:26479,192.168.0.1:26579
    lettuce:
      pool:
        max-idle: 10
        max-active: 20
        min-idle: 5
        max-wait: 10000ms

2.引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
​
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

3.代码测试

    @Autowired
    private StringRedisTemplate redisTemplate;
​
    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("k3","v3");
​
    }

执行结果

127.0.0.1:6379> get k3
"v3"127.0.0.1:6380> get k3
"v3"127.0.0.1:6381> get k3
"v3"

4.注意事项

上面的pom.xml如果不加commons-pool2数据库会报以下错误:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisConnectionFactory' defined in class path resource [org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory]: Factory method 'redisConnectionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
 at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:645) ~[spring-beans-5.2.0.RELEASE.jar:5.2.0.RELEASE]

错误提示的大概含义为:""数据库连接池没有实现,需要引入其它的实现jar包",我们找到问题的原因,添加指定的jar即可解决。

欢迎点赞关注评论,感谢观看ヾ(◍°∇°◍)ノ゙