Redis Sentinel的failover过程

Posted by Geuni's Blog on August 24, 2023

Redis Sentinel是 Redis实现高可用(HA)的组件(非集群支持)。Reids有个叫Redis Cluster产品, Sentinel与它无关,Sentinel给无需集群环境的用户提供简单的故障转移功能。

Sentinel的基本功能:

  • Monitoring : Sentinel以自动化的failover(故障转移)为目的,持续监控master/slave的状态。
  • Notification: redis实例宕机,或发生failover的时候通过pub/sub功能通知客户端,也可以通过预配置的shell script实现通知管理员的功能。
  • Automatic failover: master发生宕机后将开始执行failover流程,sentinel将挑选适合的slave将它提升为master,并通知其他的slave使用新的master。
  • Configuration provider: Sentinel给客户端提供服务发现功能,给客户端提供master, slave的信息以提供读写分离等功能。sentinel充当的是配置提供的角色而非提供代理服务。

关于Sentinel的基本知识:

  • 为提供高可用高可用最少需要3个sentinel实例,并且为了投票的效率性推荐实例数量为奇数(偶数状态下发生50%投票率将导致重新投票)。

  • 各实例应发布到相对独立的VM、物理服务器或可用区(AZ),以降低故障影响。

  • Sentinel + Redis模式下,redis间的数据同步是异步方式进行的,故障转移有可能会导致数据的丢失。

  • 所有构建的HA架构应做充分的验证测试,没有验证有效性的HA架构不能认定为安全的。

两种下线状态: S_DOWN, O_DOWN

  • S_DOWN(Subjectively Down 主观下线) - 本地单个sentinel没有接收到redis实例的PING响应,此时会进入S_DOWN状态。
  • O_DOWN(Objectively Down 客观下线) - sentinel判定某个实例为S_DOWN后,会通过is-master-down-by-addr命令询问其他sentinel对master的下线与否判定情况,当超出已设定quorum数的sentinel回复S_DOWN状态,下线状态将从S_DOWN -> 升级为ODOWN状态。

O_DOWN状态是failover的触发条件,仅适用于master实例。slave宕机时sentinel不做任何操作,当然不会有O_DOWN状态。

failover过程

Sentinel会定时对master, slave做healthcheck。

对master实例做出O_DOWN判定后(src@sentinelCheckSubjectivelyDown)将启动failover流程。

首选会选举一个执行failover操作的sentinel leader,被选为leader的sentinel会经过一系列筛选,优先级计算等操作后选出一个master后进行主备切换。

failover过程中会经过如下几个failover状态的变化。 (参考sentinel.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 +-------------------------------------------+
 | SENTINEL_FAILOVER_STATE_NONE              |               
 |  - No failover in progress                | 			
 +-------------------------------------------+
                      ↓
 +-------------------------------------------+
 | SENTINEL_FAILOVER_STATE_WAIT_START        |		 
 | - Wait for failover_start_time            | 			 
 +-------------------------------------------+
                       ↓
 +-------------------------------------------+
 | SENTINEL_FAILOVER_STATE_SELECT_SLAVE      | 	 
 | - Select slave to promote                 |				 
 +-------------------------------------------+
                       ↓
 +-------------------------------------------+
 | SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOON | 
 | - Slave -> Master                         |						 
 +-------------------------------------------+
                       ↓
 +-------------------------------------------+
 | SENTINEL_FAILOVER_STATE_WAIT_PROMOTION    |
 | - Wait slave to change role               | 				 
 +-------------------------------------------+
                       ↓
 +-------------------------------------------+
 | SENTINEL_FAILOVER_STATE_RECONF_SLAVES     |     
 | - SLAVEOF newmaster                       |						 
 +-------------------------------------------+
                        ↓
 +-------------------------------------------+
 | SENTINEL_FAILOVER_STATE_UPDATE_CONFIG     |  	 
 | - Monitor promoted slave.                 |				 
 +-------------------------------------------+
 

Sentinel leader 选举过程

  1. Sentinel对master判定为O_DOWN状态后开始failover流程。
  2. current-epoch +1后给其他sentinel发送SENTINEL IS-MASTER-DOWN-BY-ADDR <ip> <port> <current-epoch> <runid>命令以请求投票(src@sentinelAskMasterStateToOtherSentinels)
  3. 接收到投票(is-master-down-by-addr)请求的sentinel会比较请求过来的 req_epoch和自身的current-epoch。如果req_epoch大于 current-epoch将更新master结构体的leader,leader_epoch为请求过来的 req_runid, req_epoch。如果req_epoch等于current-epoch说明接收请求的sentinel已经投过票了,将返回已经投过票的sentinel的 runid。 (src@sentinelVoteLeader)
  4. 选举期间内一半以上sentinel投过票并最少获得quorum数的投票以后将被选举为leader,此时failover_state将变更为SENTINEL_FAILOVER_STATE_SELECT_SLAVE.src@sentinelFailoverWaitStart
    • 需最少需要获得50% + 1的票数才能成为leader,这也是为什么建议sentinel实例数为奇数的原因
  5. 选举期间内如果没有当选的leader,将经过 (failover_timeout * 2)时间后重新开始选举.
  6. 当选的leader将执行新master的选出及替换工作.

master 选出

master选出过程开始后,将首先剔除不适合当作master的slave后通过优先级的计算选择最终的master。

将通过如下条件剔除不适合的slave: (src@sentinelSelectSlave)

  1. 剔除S_DOWN, O_DOWN, DISCONNECTED 状态的实例
  2. 剔除5秒(sentinel_ping_period*5) 内没有PING响应的实例。
  3. 剔除slave_priority(优先顺序)为0的实例 (priority=0的当作废弃实例)
  4. 剔除info_validity_time为3秒以前或5秒(master为S_DOWN状态时)以前的实例
  5. 剔除master_link_down_time大于 (now - master->s_down_since_time) + (master->down_after_period * 10) 的实例

剔除不适合的slave以后,将通过如下顺序挑选最终的master(src@compareSlavesForPromotion )

  1. slave_priority小的优先
  2. replication offset(slave_repl_offset)大的优先
  3. runid小的优先

找到适合的slave后failover_state将变更为SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE。(src@sentinelFailoverSelectSlave)

slave to master

找到适合(状态最优)的slave后将开始实际的主备切换工作。

  1. SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE状态 在failover_state为SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE状态下,sentinel将发送 SLAVEOF NO ONE命令给被选的slave,让其role变更为master。而后failover_state将变更为SENTINEL_FAILOVER_STATE_WAIT_PROMOTION。(src@sentinelFailoverSendSlaveOfNoOne)

  2. SENTINEL_FAILOVER_STATE_WAIT_PROMOTION状态 sentinel发送SLAVEOF NO ONE 命令后,使用INFO命令确认slave的role是否变更为master。failover_timeout内slave变更为master以后failover_state变更为SENTINEL_FAILOVER_STATE_RECONF_SLAVES。(src@sentinelGetCurrentMasterAddress)

  3. SENTINEL_FAILOVER_STATE_RECONF_SLAVES로状态 给其他slave发送SLAVEOF <new master>命令,然后failover_state变更为SENTINEL_FAILOVER_STATE_UPDATE_CONFIG。(src@sentinelFailoverDetectEnd)

  4. SENTINEL_FAILOVER_STATE_UPDATE_CONFIG状态 内存中的master信息替换为新master信息后重写redis的redis.conf文件。src@sentinelResetMasterAndChangeAddress

    failover_state重新恢复为SENTINEL_FAILOVER_STATE_NONE,failover流程结束。

Sentinel的通信

所有sentinel有如下通信对象:

  • 监控中的master
  • 所有与master连接的slave
  • 所有与master连接的其他sentinel

我们都知道sentinel.conf只配置了需要监控的master信息。 那sentinel与slave跟其他sentinel是如何通信的 ?

Sentinel如何与Slave通信的?

Sentinel은 master에 INFO명령을 날려 slave정보를 조회한다.(src@sentinelRefreshInstanceInfo) Sentinel是通过给master发送INFO命令查询slave的信息。(src@sentinelRefreshInstanceInfo)

Sentinel间是如何通信的?

Sentinel间的通信是通过redis的Pub/Sub功能实现的。master有个__sentinel__:hellosentinel专用渠道,用来发布自己的ip, 端口等信息。(src@sentinelSendHello)

  • 消息格式: sentinel_ip,sentinel_port,sentinel_runid,current_epoch, master_name,master_ip,master_port,master_config_epoch

sentinel读取订阅中的__sentinel__:hello渠道的消息后将首先确认runid。runid与自己的runid一样时,消息判定为自己的发布的消息而废弃掉。runid不一样则记录其他sentinel的信息到字典中。(src@sentinelReceiveHelloMessages)