在 Redis 主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。在这个过程中,不仅需要人为干预,而且还会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性较低,不适用于线上生产环境。

Redis 官方推荐一种高可用方案,也就是 Redis Sentinel 哨兵模式,它弥补了主从模式的不足。Sentinel 通过监控的方式获取主机的工作状态是否正常,当主机发生故障时, Sentinel 会自动进行 Failover(即故障转移),并将其监控的从机提升主服务器(master),从而保证了系统的高可用性

哨兵模式的原理

哨兵模式是一种特殊的模式,Redis 为其提供了专属的哨兵命令,它是一个独立的进程,能够独立运行。下面使用 Sentinel 搭建 Redis 集群,基本结构图如下所示:

在这里插入图片描述

1.1 哨兵的作用

  • master 状态检测;
  • 如果 master 异常,则会进行 master-slave 切换,将其中一个 slave 作为 master,将之前的 master 作为 slave;
  • master-slave 切换后,master_redis.conf、slave_redis.conf 和 sentinel.conf 的内容都会发生改变,即 master_redis.conf 中会多一行 slaveof 的配置,sentinel.conf 的监控目标会随之调换。

1.2 哨兵会定时发送三个任务:

1)每10秒每个sentinel会对master和slave执行info命令,这个任务达到两个目的:
a)发现slave节点
b)确认主从关系
2)每2秒每个sentinel通过master节点的channel交换信息(pub/sub)。master节点上有一个发布订阅的频道(sentinel:hello)。sentinel节点通过__sentinel__:hello频道进行信息交换(对节点的"看法"和自身的信息),达成共识。
3)每1秒每个sentinel对其他sentinel和redis节点执行ping操作(相互监控),这个其实是一个心跳检测,是失败判定的依据。

1.3 工作方式

  1. 每个 sentinel 以每秒钟一次的频率向它所知的 master,slave 以及其他 sentinel 实例发送一个 ping 命令;
  2. 如果一个实例(instance)距离最后一次有效回复 ping 命令的时间超过 own-after-milliseconds 选项所指定的值,则这个实例会被 sentinel 标记为主观下线;
  3. 如果一个 master 被标记为主观下线,则正在监视这个 master 的所有 sentinel 要以每秒一次的频率确认 master 的确进入了主观下线状态;
  4. 当有足够数量的 sentinel(大于等于配置文件指定的值,quorum,默认半数以上)在指定的时间范围内确认 master 的确进入了主观下线状态,则 master 会被标记为客观下线;
  5. 在一般情况下,每个 sentinel 会以每 10 秒一次的频率向它已知的所有 master,slave 发送 info 命令;
  6. 当 master 被 sentinel 标记为客观下线时,sentinel 向下线的 master 的所有 slave 发送 info 命令的频率会从 10 秒一次改为每秒一次;
  7. 若没有足够数量的 sentinel 同意 master 已经下线,master 的客观下线状态就会被移除。 若 master 重新向 sentinel 的 ping 命令返回有效回复,master 的主观下线状态就会被移除。

主观下线:
所谓主观下线(Subjectively Down, 简称 SDOWN)指的是单个Sentinel实例对服务器做出的下线判断,即单个sentinel认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。
主观下线就是说如果服务器在down-after-milliseconds给定的毫秒数之内, 没有返回 Sentinel 发送的 PING 命令的回复, 或者返回一个错误, 那么 Sentinel 将这个服务器标记为主观下线(SDOWN )。
sentinel会以每秒一次的频率向所有与其建立了命令连接的实例(master,从服务,其他sentinel)发ping命令,通过判断ping回复是有效回复,还是无效回复来判断实例时候在线(对该sentinel来说是“主观在线”)。
sentinel配置文件中的down-after-milliseconds设置了判断主观下线的时间长度,如果实例在down-after-milliseconds毫秒内,返回的都是无效回复,那么sentinel回认为该实例已(主观)下线,修改其flags状态为SRI_S_DOWN。如果多个sentinel监视一个服务,有可能存在多个sentinel的down-after-milliseconds配置不同,这个在实际生产中要注意。

客观下线:
客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断,然后开启failover。
客观下线就是说只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线(ODOWN)。
只有当master被认定为客观下线时,才会发生故障迁移。
当sentinel监视的某个服务主观下线后,sentinel会询问其它监视该服务的sentinel,看它们是否也认为该服务主观下线,接收到足够数量(这个值可以配置)的sentinel判断为主观下线,既任务该服务客观下线,并对其做故障转移操作。
sentinel通过发送 SENTINEL is-master-down-by-addr ip port current_epoch runid,(ip:主观下线的服务id,port:主观下线的服务端口,current_epoch:sentinel的纪元,runid:*表示检测服务下线状态,如果是sentinel 运行id,表示用来选举领头sentinel)来询问其它sentinel是否同意服务下线。
一个sentinel接收另一个sentinel发来的is-master-down-by-addr后,提取参数,根据ip和端口,检测该服务时候在该sentinel主观下线,并且回复is-master-down-by-addr,回复包含三个参数:down_state(1表示已下线,0表示未下线),leader_runid(领头sentinal id),leader_epoch(领头sentinel纪元)。
sentinel接收到回复后,根据配置设置的下线最小数量,达到这个值,既认为该服务客观下线。
客观下线条件只适用于主服务器: 对于任何其他类型的 Redis 实例, Sentinel 在将它们判断为下线前不需要进行协商, 所以从服务器或者其他 Sentinel 永远不会达到客观下线条件。只要一个 Sentinel 发现某个主服务器进入了客观下线状态, 这个 Sentinel 就可能会被其他 Sentinel 推选出, 并对失效的主服务器执行自动故障迁移操作。

主观下线(SDOWN)和客观下线(ODOWN)的更多细节
sentinel对于不可用有两种不同的看法,一个叫主观不可用(SDOWN),另外一个叫客观不可用(ODOWN)。SDOWN是sentinel自己主观上检测到的关于master的状态,ODOWN需要一定数量的sentinel达成一致意见才能认为一个master客观上已经宕掉,各个sentinel之间通过命令SENTINEL is_master_down_by_addr来获得其它sentinel对master的检测结果。
从sentinel的角度来看,如果发送了PING心跳后,在一定时间内没有收到合法的回复,就达到了SDOWN的条件。这个时间在配置中通过is-master-down-after-milliseconds参数配置。
当sentinel发送PING后,以下回复之一都被认为是合法的:
PING replied with +PONG.
PING replied with -LOADING error.
PING replied with -MASTERDOWN error.
其它任何回复(或者根本没有回复)都是不合法的。

从SDOWN切换到ODOWN不需要任何一致性算法,只需要一个gossip协议:如果一个sentinel收到了足够多的sentinel发来消息告诉它某个master已经down掉了,SDOWN状态就会变成ODOWN状态。如果之后master可用了,这个状态就会相应地被清理掉。
正如之前已经解释过了,真正进行failover需要一个授权的过程,但是所有的failover都开始于一个ODOWN状态。
ODOWN状态只适用于master,对于不是master的redis节点sentinel之间不需要任何协商,slaves和sentinel不会有ODOWN状态。

配置版本号

为什么要先获得大多数sentinel的认可时才能真正去执行failover呢?
当一个sentinel被授权后,它将会获得宕掉的master的一份最新配置版本号,当failover执行结束以后,这个版本号将会被用于最新的配置。因为大多数sentinel都已经知道该版本号已经被要执行failover的sentinel拿走了,所以其他的sentinel都不能再去使用这个版本号。这意味着,每次failover都会附带有一个独一无二的版本号。我们将会看到这样做的重要性。而且,sentinel集群都遵守一个规则:如果sentinel A推荐sentinel B去执行failover,B会等待一段时间后,自行再次去对同一个master执行failover,这个等待的时间是通过failover-timeout配置项去配置的。从这个规则可以看出,sentinel集群中的sentinel不会再同一时刻并发去failover同一个master,第一个进行failover的sentinel如果失败了,另外一个将会在一定时间内进行重新进行failover,以此类推。
redis sentinel保证了活跃性:如果大多数sentinel能够互相通信,最终将会有一个被授权去进行failover.
redis sentinel也保证了安全性:每个试图去failover同一个master的sentinel都会得到一个独一无二的版本号。

配置传播

一旦一个sentinel成功地对一个master进行了failover,它将会把关于master的最新配置通过广播形式通知其它sentinel,其它的sentinel则更新对应master的配置。
一个faiover要想被成功实行,sentinel必须能够向选为master的slave发送SLAVEOF NO ONE命令,然后能够通过INFO命令看到新master的配置信息。
当将一个slave选举为master并发送SLAVEOF NO ONE后,即使其它的slave还没针对新master重新配置自己,failover也被认为是成功了的,然后所有sentinels将会发布新的配置信息。
新配在集群中相互传播的方式,就是为什么我们需要当一个sentinel进行failover时必须被授权一个版本号的原因。
每个sentinel使用##发布/订阅##的方式持续地传播master的配置版本信息,配置传播的##发布/订阅##管道是:sentinel:hello。
因为每一个配置都有一个版本号,所以以版本号最大的那个为标准。

举个例子:
假设有一个名为mymaster的地址为192.168.10.202:6379。一开始,集群中所有的sentinel都知道这个地址,于是为mymaster的配置打上版本号1。一段时候后mymaster死了,有一个sentinel被授权用版本号2对其进行failover。如果failover成功了,假设地址改为了192.168.10.202:9000,此时配置的版本号为2,进行failover的sentinel会将新配置广播给其他的sentinel,由于其他sentinel维护的版本号为1,发现新配置的版本号为2时,版本号变大了,说明配置更新了,于是就会采用最新的版本号为2的配置。
这意味着sentinel集群保证了第二种活跃性:一个能够互相通信的sentinel集群最终会采用版本号最高且相同的配置。

sentinel的"仲裁会"
前面我们谈到,当一个master被sentinel集群监控时,需要为它指定一个参数,这个参数指定了当需要判决master为不可用,并且进行failover时,所需要的sentinel数量,可以称这个参数为票数

不过,当failover主备切换真正被触发后,failover并不会马上进行,还需要sentinel中的大多数sentinel授权后才可以进行failover。
当ODOWN时,failover被触发。failover一旦被触发,尝试去进行failover的sentinel会去获得“大多数”sentinel的授权(如果票数比大多数还要大的时候,则询问更多的sentinel)
这个区别看起来很微妙,但是很容易理解和使用。例如,集群中有5个sentinel,票数被设置为2,当2个sentinel认为一个master已经不可用了以后,将会触发failover,但是,进行failover的那个sentinel必须先获得至少3个sentinel的授权才可以实行failover。
如果票数被设置为5,要达到ODOWN状态,必须所有5个sentinel都主观认为master为不可用,要进行failover,那么得获得所有5个sentinel的授权。

1.4 哨兵的个数

当哨兵判定某个节点为客观下线后,就会向其他哨兵发起投票,选出一个leader哨兵,最后由这个leader哨兵来进行主从切换

所以我们一般要求哨兵节点为2个及以上,且为奇数,也就是至少3个。想想为什么?

因为如果为偶数个的哨兵节点时,就会出现"脑裂"问题,这个在es集群中也比较常见,比如一半的哨兵投了反对票, 一半的投了支持票,请问最终结果如何?

其次因为哨兵需要半数以上投票通过才能通过,当为2节点时。如果宕机了一个节点,就永远只有一个节点,则永远不能超过半数以上了,而为2节点时,即使宕机1个,还有2个节点可以投票,也能满足半数以上。

所以,我们必然需要奇数个哨兵才能真正投票出结果

2461:X 18 Jul 08:29:19.276 #    +sdown master mymaster 127.0.0.1 6380【  master服务已经宕机  】
2461:X 18 Jul 08:29:19.276 # +odown master mymaster 127.0.0.1 6380 #quorum 1/1
2461:X 18 Jul 08:29:19.276 # +new-epoch 1
2461:X 18 Jul 08:29:19.276 #    +try-failover master mymaster 127.0.0.1 6380【尝试恢复master】
2461:X 18 Jul 08:29:19.282 # +vote-for-leader 236f14b361fc5a0dc0621cf88823ed6e6252b2f3 1  【  投票选举哨兵leader,现在就一个哨兵所以leader就自己  】
2461:X 18 Jul 08:29:19.282 # +elected-leader master mymaster 127.0.0.1 6380  【选出leader】
2461:X 18 Jul 08:29:19.282 # +failover-state-select-slave master mymaster 127.0.0.1 6380 【选中其中的一个slave当做master】
2461:X 18 Jul 08:29:19.345 #    +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380【选中6381当master】
2461:X 18 Jul 08:29:19.345 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380  【发送slaveof no one命令】
2461:X 18 Jul 08:29:19.401 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380  【等待升级master】
2461:X 18 Jul 08:29:20.291 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380  【升级6381为master】
2461:X 18 Jul 08:29:20.291 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6380
2461:X 18 Jul 08:29:20.361 * +slave-reconf-sent slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
2461:X 18 Jul 08:29:20.666 * +slave-reconf-inprog slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
2461:X 18 Jul 08:29:21.694 * +slave-reconf-done slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
2461:X 18 Jul 08:29:21.770 #    +failover-end master mymaster 127.0.0.1 6380【  故障恢复完成 】
2461:X 18 Jul 08:29:21.770 #    +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6381【master 从6380切换到63812461:X 18 Jul 08:29:21.771 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6381  【添加63826381的从库】
2461:X 18 Jul 08:29:21.771 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381  【添加63806381的从库】
2461:X 18 Jul 08:29:51.828 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381  【  发现6380已经宕机,等待6380的恢复  】

1.5 哨兵的投票机制

但奇数个哨兵就万事大吉了吗?如果每个哨兵都投个自己,那不是没法选出一个leader了吗?

redis哨兵的投票算法当然想到了这点。

一个redis服务被判断为客观下线时,多个监视该服务的sentinel协商,选举一个领头sentinel,对该redis服务进行故障转移操作。选举领头sentinel遵循以下规则:

  1. 所有的sentinel都有公平被选举成领头的资格
  2. 所有的sentinel都有且只有一次将某个sentinel选举成领头的机会(在一轮选举中),一旦选举某个sentinel为领头,不能更改。
  3. sentinel设置领头sentinel是先到先得,一旦当前sentinel设置了领头sentinel,以后要求设置sentinel为领头请求都会被拒绝。
  4. 每个发现服务客观下线的sentinel,都会要求其他sentinel将自己设置成领头。
  5. 当一个sentinel(源sentinel)向另一个sentinel(目sentinel)发送is-master-down-by-addr ip port current_epoch runid命令的时候,runid参数不是*,而是sentinel运行id,就表示源sentinel要求目标sentinel选举其为领头。
  6. 源sentinel会检查目标sentinel对其要求设置成领头的回复,如果回复的leader_runid和leader_epoch为源sentinel,表示目标sentinel同意将源sentinel设置成领头。
  7. 如果某个sentinel被半数以上的sentinel设置成领头,那么该sentinel既为领头。
  8. 如果在限定时间内,没有选举出领头sentinel,暂定一段时间,再选举。

sentinel集群注意事项

  1. 只有Sentinel 集群中大多数服务器认定master主观下线时master才会被认定为客观下线,才可以进行故障迁移,也就是说,即使不管我们在sentinel monitor中设置的数是多少,就算是满足了该值,只要达不到大多数,就不会发生故障迁移。
  2. 官方建议sentinel至少部署三台,且分布在不同机器。这里主要考虑到sentinel的可用性,假如我们只部署了两台sentinel,且quorum设置为1,也可以实现自动故障迁移,但假如其中一台sentinel挂了,就永远不会触发自动故障迁移,因为永远达不到大多数sentinel认定master主观下线了。
  3. sentinel monitor配置中的master IP尽量不要写127.0.0.1或localhost,因为客户端,如jedis获取master是根据这个获取的,若这样配置,jedis获取的ip则是127.0.0.1,这样就可能导致程序连接不上master
  4. 当sentinel 启动后会自动的修改sentinel.conf文件,如已发现的master的slave信息,和集群中其它sentinel 的信息等,这样即使重启sentinel也能保持原来的状态。注意,当集群服务器调整时,如更换sentinel的机器,或者新配置一个sentinel,请不要直接复制原来运行过得sentinel配置文件,因为其里面自动生成了以上说的那些信息,我们应该复制一个新的配置文件或者把自动生成的信息给删掉。
  5. 当发生故障迁移的时候,master的变更记录与slave更换master的修改会自动同步到redis的配置文件,这样即使重启redis也能保持变更后的状态。

Sentinel之间和Slaves之间的自动发现机制

  1. 虽然sentinel集群中各个sentinel都互相连接彼此来检查对方的可用性以及互相发送消息。但是你不用在任何一个sentinel配置任何其它的sentinel的节点。因为sentinel利用了master的发布/订阅机制去自动发现其它也监控了统一master的sentinel节点。通过向名为__sentinel__:hello的管道中发送消息来实现。
  2. 同样,你也不需要在sentinel中配置某个master的所有slave的地址,sentinel会通过询问master来得到这些slave的地址的。
  3. 每个sentinel通过向每个master和slave的发布/订阅频道__sentinel__:hello每秒发送一次消息,来宣布它的存在。
  4. 每个sentinel也订阅了每个master和slave的频道__sentinel__:hello的内容,来发现未知的sentinel,当检测到了新的sentinel,则将其加入到自身维护的master监控列表中。
  5. 每个sentinel发送的消息中也包含了其当前维护的最新的master配置。如果某个sentinel发现自己的配置版本低于接收到的配置版本,则会用新的配置更新自己的master配置。
  6. 在为一个master添加一个新的sentinel前,sentinel总是检查是否已经有sentinel与新的sentinel的进程号或者是地址是一样的。如果是那样,这个sentinel将会被删除,而把新的sentinel添加上去。

sentinel和redis身份验证

当一个master配置为需要密码才能连接时,客户端和slave在连接时都需要提供密码。
master通过requirepass设置自身的密码,不提供密码无法连接到这个master。
slave通过masterauth来设置访问master时的密码。
但是当使用了sentinel时,由于一个master可能会变成一个slave,一个slave也可能会变成master,所以需要同时设置上述两个配置项。

redis哨兵,原master宕机重启后无法与新master主从同步

redis哨兵模式下,当master宕机之后,哨兵sentinel会自动将某一slave升级成新的master,原master重启之后应该自动成为新master的slave节点。但我们发现,原master重启之后,知道新的master是谁,但是却无法建立连接,主要表现在,原master中使用info命令查看节点信息时,发现Replication->master_link_status: down

我们的redis节点本身是配置了密码的,即配置文件中requirepass配置项值不为空,所以各节点进行主从配置的时候,配置文件中需要通过masterauth配置项指定连接master的密码。但当原master宕机,sentinel重新选举出新master时,sentinel只会往各节点配置文件replicaof配置项中写入新的master地址,但是masterauth配置项却不会更新,所以我们只需要通过原master配置文件中masterauth配置项指定连接新master的密码即可。

所以,搭建redis哨兵时,如果所在网络安全,各节点可以不设置密码,如果必须设置密码,建议各节点密码设置成相同,且在初始配置时各节点配置文件中就加上masterauth配置项。

1.6 哨兵主从切换

当选举出哨兵Leader后,就可以进行主从切换了。

1、从挂掉的主节点下的所有的从节点中选一个状态健康、优先级高且数据完整的从节点。

所谓状态健康就是网络连接正常的,主要是主从节点在down-after-milliseconds 时间内产生过网络通信的即可认为是健康的从节点。

而优先级高则是根据slave-priority配置的值来判定的

数据完整则是当优先级相同,复制的数据下标越多则数据越完整(主从复制时从节点会记录复制的数据下标),如果优先级和数据完整都想同时就会选择节点ID最小的那个

这里大家可能会想:“哨兵是怎么知道主节点下有哪些从节点的,主节点不是都挂掉了吗?”
答:还记得我们上面讲的哨兵的三个定时任务吗?哨兵就是通过第一个定时任务获取的从节点信息,并且记录在册,以供使用。

2、然后通过指令slaveof no one清除这个从节点的SLAVE信息,也就是取消掉其附属的主节点,让其成为主节点。

3、同时将挂掉的主节点的所有从节点信息再挂载到新的主节点上

4、至此,内部的主从转换就完成了,但下一步我们还需要将新主节点的信息广播给客户端。当然这里的广播是通过redis的发布/订阅模式来实现的

5、还没完,旧的主节点哨兵也会持续监听,当它恢复后,哨兵会将其作为从节点挂载到新主节点上。所以这里要注意,主节点宕机后再恢复,并不会自动再切回主节点,而是作为从节点存在

哨兵模式搭建

1、首先准备三个节点,一主双从,并启动。

2、可以单独启动redis节点作为哨兵节点,也可以配置到原有的数据节点(主从节点)中,这里为了节约服务器资源,我们将哨兵配置到原来的主从节点中

3、哨兵有一个单独的配置文件sentinel.conf,在redis的安装目录下可以看到

4、修改三个节点的配置文件

vim sentinel.conf 

修改内容:

# 关闭保护模式
protected-mode no
# 哨兵监听端口,默认26379
port 26379
# 后台启动,即开启守护进程
daemonize yes
# 日志路径
logfile "/var/local/redis/logs/sentinel.log"
# 指定数据存放路径,默认/tmp
dir "/tmp"
# 监听的主节点 mymaster为主节点名称,最后的2为quorum值,
# quorum的值一般设置为sentinel个数的二分之一加1,例如3个sentinel就设置2。
sentinel monitor mymaster 192.168.99.203 6379 2
# 如果master节点有密钥要补充密码
#sentinel auth-pass mymaster xxx
# 判定服务器down掉的时间周期,默认30000毫秒(30秒)
sentinel down-after-milliseconds mymaster 30000
# 故障节点的最大超时时间为180000180秒)
sentinel failover-timeout mymaster 180000

5、如果未关闭防火墙,需要开启哨兵监听端口

# 查询端口是否开放
firewall-cmd --query-port=26379/tcp
# 开启端口
firewall-cmd --add-port=26379/tcp --permanent
# 开启后重新加载
firewall-cmd --reload

6、启动哨兵

cd redis安装目录
redis-sentinel sentinel.conf

测试

将主节点进程杀死以后,查看哨兵日志

在这里插入图片描述
可以看到主节点已经切换到111的节点了

当再次吧原来的主节点启动起来
在这里插入图片描述
发现原主节点变成了从节点

增加或删除Sentinel

由于有sentinel自动发现机制,所以添加一个sentinel到你的集群中非常容易,你所需要做的只是监控到某个Master上,然后新添加的sentinel就能获得其他sentinel的信息以及master所有的slaves。
如果你需要添加多个sentinel,建议你一个接着一个添加,这样可以预防网络隔离带来的问题。你可以每个30秒添加一个sentinel。最后你可以用SENTINEL MASTER mastername来检查一下是否所有的sentinel都已经监控到了master。
删除一个sentinel显得有点复杂:因为sentinel永远不会删除一个已经存在过的sentinel,即使它已经与组织失去联系很久了。
要想删除一个sentinel,应该遵循如下步骤:

  1. 停止所要删除的sentinel
  2. 发送一个SENTINEL RESET * 命令给所有其它的sentinel实例,如果你想要重置指定master上面的sentinel,只需要把*号改为特定的名字,注意,需要一个接一个发,每次发送的间隔不低于30秒。
  3. 检查一下所有的sentinels是否都有一致的当前sentinel数。使用SENTINEL MASTER mastername 来查询。

删除旧master或者不可达slave

sentinel永远会记录好一个Master的slaves,即使slave已经与组织失联好久了。这是很有用的,因为sentinel集群必须有能力把一个恢复可用的slave进行重新配置。并且,failover后,失效的master将会被标记为新master的一个slave,这样的话,当它变得可用时,就会从新master上复制数据。然后,有时候你想要永久地删除掉一个slave(有可能它曾经是个master),你只需要发送一个SENTINEL RESET master命令给所有的sentinels,它们将会更新列表里能够正确地复制master数据的slave。

Slave选举与优先级

当一个sentinel准备好了要进行failover,并且收到了其他sentinel的授权,那么就需要选举出一个合适的slave来做为新的master。
slave的选举主要会评估slave的以下几个方面:

  1. 与master断开连接的次数
  2. Slave的优先级
  3. 数据复制的下标(用来评估slave当前拥有多少master的数据)
  4. 进程ID

如果一个slave与master失去联系超过10次,并且每次都超过了配置的最大失联时间(down-after-milliseconds),如果sentinel在进行failover时发现slave失联,那么这个slave就会被sentinel认为不适合用来做新master的。
更严格的定义是,如果一个slave持续断开连接的时间超过
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
就会被认为失去选举资格。

符合上述条件的slave才会被列入master候选人列表,并根据以下顺序来进行排序:

  1. sentinel首先会根据slaves的优先级来进行排序,优先级越小排名越靠前。
  2. 如果优先级相同,则查看复制的下标,哪个从master接收的复制数据多,哪个就靠前。
  3. 如果优先级和下标都相同,就选择进程ID较小的那个。

一个redis无论是master还是slave,都必须在配置中指定一个slave优先级。要注意到master也是有可能通过failover变成slave的。
如果一个redis的slave优先级配置为0,那么它将永远不会被选为master。但是它依然会从master哪里复制数据。

故障转移

所谓故障转移就是当master宕机,选一个合适的slave来晋升为master的操作,redis-sentinel会自动完成这个,不需要我们手动来实现。

一次故障转移操作大致分为以下流程:
发现主服务器已经进入客观下线状态。
对我们的当前集群进行自增, 并尝试在这个集群中当选。
如果当选失败, 那么在设定的故障迁移超时时间的两倍之后, 重新尝试当选。 如果当选成功, 那么执行以下步骤:

  1. 选出一个从服务器,并将它升级为主服务器。
  2. 向被选中的从服务器发送 SLAVEOF NO ONE 命令,让它转变为主服务器。
  3. 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。
  4. 向已下线主服务器的从服务器发送 SLAVEOF 命令, 让它们去复制新的主服务器。
  5. 当所有从服务器都已经开始复制新的主服务器时, 领头 Sentinel 终止这次故障迁移操作。
  6. 每当一个 Redis 实例被重新配置(reconfigured) —— 无论是被设置成主服务器、从服务器、又或者被设置成其他主服务器的从服务器 —— Sentinel 都会向被重新配置的实例发送一个 CONFIG REWRITE 命令, 从而确保这些配置会持久化在硬盘里。

Sentinel 使用以下规则来选择新的主服务器:

  1. 在失效主服务器属下的从服务器当中, 那些被标记为主观下线、已断线、或者最后一次回复 PING 命令的时间大于五秒钟的从服务器都会被淘汰。
  2. 在失效主服务器属下的从服务器当中, 那些与失效主服务器连接断开的时长超过 down-after 选项指定的时长十倍的从服务器都会被淘汰。
  3. 在经历了以上两轮淘汰之后剩下来的从服务器中, 我们选出复制偏移量(replication offset)最大的那个从服务器作为新的主服务器; 如果复制偏移量不可用, 或者从服务器的复制偏移量相同, 那么带有最小运行 ID 的那个从服务器成为新的主服务器。

Sentinel 自动故障迁移的一致性特质

Sentinel 自动故障迁移使用 Raft 算法来选举领头(leader) Sentinel , 从而确保在一个给定的纪元(epoch)里, 只有一个领头产生。

这表示在同一个纪元中, 不会有两个 Sentinel 同时被选中为领头, 并且各个 Sentinel 在同一个纪元中只会对一个领头进行投票。

更高的配置纪元总是优于较低的纪元, 因此每个 Sentinel 都会主动使用更新的纪元来代替自己的配置。

简单来说, 可以将 Sentinel 配置看作是一个带有版本号的状态。 一个状态会以最后写入者胜出(last-write-wins)的方式(也即是,最新的配置总是胜出)传播至所有其他 Sentinel 。

举个例子, 当出现网络分割(network partitions)时, 一个 Sentinel 可能会包含了较旧的配置, 而当这个 Sentinel 接到其他 Sentinel 发来的版本更新的配置时, Sentinel 就会对自己的配置进行更新。

如果要在网络分割出现的情况下仍然保持一致性, 那么应该使用 min-slaves-to-write 选项, 让主服务器在连接的从实例少于给定数量时停止执行写操作, 与此同时, 应该在每个运行 Redis 主服务器或从服务器的机器上运行 Redis Sentinel 进程。

Sentinel 状态的持久化

Sentinel 的状态会被持久化在 Sentinel 配置文件里面。每当 Sentinel 接收到一个新的配置, 或者当领头 Sentinel 为主服务器创建一个新的配置时, 这个配置会与配置纪元一起被保存到磁盘里面。这意味着停止和重启 Sentinel 进程都是安全的。

Sentinel 在非故障迁移的情况下对实例进行重新配置
即使没有自动故障迁移操作在进行, Sentinel 总会尝试将当前的配置设置到被监视的实例上面。 特别是:

根据当前的配置, 如果一个从服务器被宣告为主服务器, 那么它会代替原有的主服务器, 成为新的主服务器, 并且成为原有主服务器的所有从服务器的复制对象。
那些连接了错误主服务器的从服务器会被重新配置, 使得这些从服务器会去复制正确的主服务器。
不过, 在以上这些条件满足之后, Sentinel 在对实例进行重新配置之前仍然会等待一段足够长的时间, 确保可以接收到其他 Sentinel 发来的配置更新, 从而避免自身因为保存了过期的配置而对实例进行了不必要的重新配置。

总结来说,故障转移分为三个步骤:

1.从下线的主服务的所有从服务里面挑选一个从服务,将其转成主服务
sentinel状态数据结构中保存了主服务的所有从服务信息,领头sentinel按照如下的规则从从服务列表中挑选出新的主服务;
删除列表中处于下线状态的从服务;
删除最近5秒没有回复过领头sentinel info信息的从服务;
删除与已下线的主服务断开连接时间超过 down-after-milliseconds*10毫秒的从服务,这样就能保留从的数据比较新(没有过早的与主断开连接);
领头sentinel从剩下的从列表中选择优先级高的,如果优先级一样,选择偏移量最大的(偏移量大说明复制的数据比较新),如果偏移量一样,选择运行id最小的从服务。

2.已下线主服务的所有从服务改为复制新的主服务
挑选出新的主服务之后,领头sentinel 向原主服务的从服务发送 slaveof 新主服务 的命令,复制新master。

3.将已下线的主服务设置成新的主服务的从服务,当其回复正常时,复制新的主服务,变成新的主服务的从服务
同理,当已下线的服务重新上线时,sentinel会向其发送slaveof命令,让其成为新主的从。

温馨提示:还可以向任意sentinel发生sentinel failover 进行手动故障转移,这样就不需要经过上述主客观和选举的过程。

sentinel.conf文件配置参数解释

1)sentinel monitor mymaster 192.168.10.202 6379 2
Sentine监听的maste地址,第一个参数是给master起的名字,第二个参数为master IP,第三个为master端口,第四个为当该master挂了的时候,若想将该master判为失效,
在Sentine集群中必须至少2Sentine同意才行,只要该数量不达标,则就不会发生故障迁移。也就是说只要有2个sentinel认为master下线,就认为该master客观下线,
启动failover并选举产生新的master。通常最后一个参数不能多于启动的sentinel实例数。
 
这个配置是sentinel需要监控的master/slaver信息,格式为sentinel monitor <mastername> <masterIP> <masterPort> <quorum> 
其中<quorum>应该小于集群中slave的个数,当失效的节点数超过了<quorum>,则认为整个体系结构失效
 
不过要注意, 无论你设置要多少个 Sentinel 同意才能判断一个服务器失效, 一个 Sentinel 都需要获得系统中多数(majority) Sentinel 的支持, 才能发起一次自动故障迁移,
并预留一个给定的配置纪元 (configuration Epoch ,一个配置纪元就是一个新主服务器配置的版本号)。
  
换句话说, 在只有少数(minority) Sentinel 进程正常运作的情况下, Sentinel 是不能执行自动故障迁移的。
-----------------------------------------------------------------------------------------------
2)sentinel down-after-milliseconds mymaster 30000
表示master被当前sentinel实例认定为失效的间隔时间。
master在多长时间内一直没有给Sentine返回有效信息,则认定该master主观下线。也就是说如果多久没联系上redis-servevr,认为这个redis-server进入到失效(SDOWN)状态。
  
如果服务器在给定的毫秒数之内, 没有返回 Sentinel 发送的 PING 命令的回复, 或者返回一个错误, 那么 Sentinel 将这个服务器标记为主观下线(subjectively down,简称 SDOWN )。
不过只有一个 Sentinel 将服务器标记为主观下线并不一定会引起服务器的自动故障迁移: 只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线
(objectively down, 简称 ODOWN ), 这时自动故障迁移才会执行。
将服务器标记为客观下线所需的 Sentinel 数量由对主服务器的配置决定。
-----------------------------------------------------------------------------------------------
3)sentinel parallel-syncs mymaster 2
当在执行故障转移时,设置几个slave同时进行切换master,该值越大,则可能就有越多的slave在切换master时不可用,可以将该值设置为1,即一个一个来,这样在某个
slave进行切换master同步数据时,其余的slave还能正常工作,以此保证每次只有一个从服务器处于不能处理命令请求的状态。
  
parallel-syncs 选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长。
  
如果从服务器被设置为允许使用过期数据集(参见对 redis.conf 文件中对 slave-serve-stale-data 选项的说明), 那么你可能不希望所有从服务器都在同一时间向新的主服务器发送同步请求,
因为尽管复制过程的绝大部分步骤都不会阻塞从服务器, 但从服务器在载入主服务器发来的 RDB 文件时, 仍然会造成从服务器在一段时间内不能处理命令请求: 如果全部从服务器一起对新的主
服务器进行同步, 那么就可能会造成所有从服务器在短时间内全部不可用的情况出现。
 
当新master产生时,同时进行"slaveof"到新master并进行"SYNC"的slave个数。 
默认为1,建议保持默认值 
在salve执行salveof与同步时,将会终止客户端请求。 
此值较大,意味着"集群"终止客户端请求的时间总和和较大。 
此值较小,意味着"集群"在故障转移期间,多个salve向客户端提供服务时仍然使用旧数据。 
-----------------------------------------------------------------------------------------------
4)sentinel can-failover mymaster yes
在sentinel检测到O_DOWN后,是否对这台redis启动failover机制
-----------------------------------------------------------------------------------------------
5)sentinel auth-pass mymaster 20180408
设置sentinel连接的master和slave的密码,这个需要和redis.conf文件中设置的密码一样
-----------------------------------------------------------------------------------------------
6)sentinel failover-timeout mymaster 180000
failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failoer失败。 
执行故障迁移超时时间,即在指定时间内没有大多数的sentinel 反馈master下线,该故障迁移计划则失效
-----------------------------------------------------------------------------------------------
7)sentinel config-epoch mymaster 0
选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步。这个数字越小, 完成故障转移所需的时间就越长。
-----------------------------------------------------------------------------------------------
8)sentinel notification-script mymaster /var/redis/notify.sh
当failover时,可以指定一个"通知"脚本用来告知当前集群的情况。
脚本被允许执行的最大时间为60秒,如果超时,脚本将会被终止(KILL)
-----------------------------------------------------------------------------------------------
9)sentinel leader-epoch mymaster 0
同时一时间最多0个slave可同时更新配置,建议数字不要太大,以免影响正常对外提供服务。

基于以上细节知识梳理,总结出sentinel的工作原理

首先要能理解SDOWN和ODOWN这两个词的含义,上面已经详细介绍了它们俩。在此再提一下:
SDOWN:subjectively down,直接翻译的为"主观"失效,即当前sentinel实例认为某个redis服务为"不可用"状态.
ODOWN:objectively down,直接翻译为"客观"失效,即多个sentinel实例都认为master处于"SDOWN"状态,那么此时master将处于ODOWN,ODOWN可以简单理解为master已经被集群确定
为"不可用",将会开启failover.
 
SDOWN适合于master和slave,但是ODOWN只会使用于master;当slave失效超过"down-after-milliseconds",那么所有sentinel实例都会将其标记为"SDOWN"1) SDOWN与ODOWN转换过程:
每个sentinel实例在启动后,都会和已知的slaves/master以及其他sentinels建立TCP连接,并周期性发送PING(默认为1)
在交互中,如果redis-server无法在"down-after-milliseconds"时间内响应或者响应错误信息,都会被认为此redis-server处于SDOWN状态。
如果SDOWN的server为master,那么此时sentinel实例将会向其他sentinel间歇性(一秒)发送"is-master-down-by-addr <ip> <port>"指令并获取响应信息,如果足够多的
sentinel实例检测到master处于SDOWN,那么此时当前sentinel实例标记master为ODOWN...其他sentinel实例做同样的交互操作。
配置项"sentinel monitor <mastername> <masterip> <masterport> <quorum>",如果检测到master处于SDOWN状态的slave个数达到<quorum>,那么此时此sentinel实例将会认为
master处于ODOWN。每个sentinel实例将会间歇性(10)向master和slaves发送"INFO"指令,如果master失效且没有新master选出时,1秒发送一次"INFO";"INFO"的主要目的就是
获取并确认当前集群环境中slaves和master的存活情况。
 
经过上述过程后,所有的sentinel对master失效达成一致后,开始failover.
 
2) Sentinel与slaves"自动发现"机制:
在sentinel的配置文件中(local-sentinel.conf),都指定了port,此port就是sentinel实例侦听其他sentinel实例建立链接的端口.在集群稳定后,最终会每个sentinel实例之间都
会建立一个tcp链接,此链接中发送"PING"以及类似于"is-master-down-by-addr"指令集,可用用来检测其他sentinel实例的有效性以及"ODOWN""failover"过程中信息的交互.
 
在sentinel之间建立连接之前,sentinel将会尽力和配置文件中指定的master建立连接.sentinel与master的连接中的通信主要是基于pub/sub来发布和接收信息,发布的信息内容包
括当前sentinel实例的侦听端口:
+sentinel sentinel 127.0.0.1:26579 127.0.0.1 26579 .... 
 
发布的主题名称为"__sentinel__:hello";同时sentinel实例也是"订阅"此主题,以获得其他sentinel实例的信息.由此可见,环境首次构建时,在默认master存活的情况下,所有的
sentinel实例可以通过pub/sub即可获得所有的sentinel信息,此后每个sentinel实例即可以根据+sentinel信息中的"ip+port"和其他sentinel逐个建立tcp连接即可.不过需要提醒
的是,每个sentinel实例均会间歇性(5)"__sentinel__:hello"主题中发布自己的ip+port,目的就是让后续加入集群的sentinel实例也能或得到自己的信息。
根据上文,我们知道在master有效的情况下,即可通过"INFO"指令获得当前master中已有的slave列表;此后任何slave加入集群,master都会向"主题中"发布"+slave 127.0.0.1:6579 ..",
那么所有的sentinel也将立即获得slave信息,并和slave建立链接并通过PING检测其存活性.
 
补充一下,每个sentinel实例都会保存其他sentinel实例的列表以及现存的master/slaves列表,各自的列表中不会有重复的信息(不可能出现多个tcp连接),对于sentinel将使用ip+port
做唯一性标记,
对于master/slaver将使用runid做唯一性标记,其中redis-server的runid在每次启动时都不同.
 
3) Leader选举:
其实在sentinels故障转移中,仍然需要一个"Leader"来调度整个过程:master的选举以及slave的重配置和同步。当集群中有多个sentinel实例时,如何选举其中一个sentinel为leader呢?
 
在配置文件中"can-failover""quorum"参数,以及"is-master-down-by-addr"指令配合来完成整个过程。
A) "can-failover"用来表明当前sentinel是否可以参与"failover"过程,如果为"YES"则表明它将有能力参与"Leader"的选举,否则它将作为"Observer",observer参与leader选举投票但
不能被选举;
B) "quorum"不仅用来控制master ODOWN状态确认,同时还用来选举leader时最小"赞同票"数;
C) "is-master-down-by-addr",它可以用来检测"ip + port"的master是否已经处于SDOWN状态,不过此指令不仅能够获得master是否处于SDOWN,同时它还额外的返回当前sentinel
本地"投票选举"Leader信息(runid);
 
每个sentinel实例都持有其他的sentinels信息,在Leader选举过程中(当为leader的sentinel实例失效时,有可能master server并没失效,注意分开理解),sentinel实例将从所有的
sentinels集合中去除"can-failover = no"和状态为SDOWN的sentinels,在剩余的sentinels列表中按照runid按照"字典"顺序排序后,取出runid最小的sentinel实例,并将它"投票选举"Leader,并在其他sentinel发送的"is-master-down-by-addr"指令时将推选的runid追加到响应中。每个sentinel实例都会检测"is-master-down-by-addr"的响应结果,如果"投票选举"的
leader为自己,且状态正常的sentinels实例中,"赞同者"的自己的sentinel个数不小于(>=) 50% + 1,且不小与<quorum>,那么此sentinel就会认为选举成功且leader为自己。
在sentinel.conf文件中,我们期望有足够多的sentinel实例配置"can-failover yes",这样能够确保当leader失效时,能够选举某个sentinel为leader,以便进行failover。如果leader无法产生,
比如较少的sentinels实例有效,那么failover过程将无法继续.
 
4) failover过程:Leader触发failover之前,首先wait数秒(随即0~5),以便让其他sentinel实例准备和调整(有可能多个leader??),如果一切正常,那么leader就需要开始将一个salve提升为master,此slave
必须为状态良好(不能处于SDOWN/ODOWN状态)且权重值最低(redis.conf中)的,当master身份被确认后,开始failover
A"+failover-triggered": Leader开始进行failover,此后紧跟着"+failover-state-wait-start",wait数秒。
B"+failover-state-select-slave": Leader开始查找合适的slave
C"+selected-slave": 已经找到合适的slave
D"+failover-state-sen-slaveof-noone": Leader向slave发送"slaveof no one"指令,此时slave已经完成角色转换,此slave即为master
E"+failover-state-wait-promotition": 等待其他sentinel确认slave
F"+promoted-slave":确认成功
G"+failover-state-reconf-slaves": 开始对slaves进行reconfig操作。
H"+slave-reconf-sent":向指定的slave发送"slaveof"指令,告知此slave跟随新的master
I"+slave-reconf-inprog": 此slave正在执行slaveof + SYNC过程,如过slave收到"+slave-reconf-sent"之后将会执行slaveof操作。
J"+slave-reconf-done": 此slave同步完成,此后leader可以继续下一个slave的reconfig操作。循环GK"+failover-end": 故障转移结束
L"+switch-master":故障转移成功后,各个sentinel实例开始监控新的master。

主要参考:
https://www.cnblogs.com/kevingrace/p/9004460.html

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐