0%

redis07_Sentinel

Sentinel

概述

Sentinel(哨岗、哨兵)是 Redis的高可用性( high availability)解决方案:由一个多个Sentinel实例组成的 Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

ps : 哨兵需要保证自己的高可用性,所以一般最少需要三个哨兵实例(
进行故障转移的时候需要选举领头Sentinel,两个哨兵节点无法保证选举成功。)

Sentinel启动

Sentinel本质上只是一个运行在特殊模式下的Redis服务器,启动的时候会将部分普通Redis服务器使用的代码替换成Sentinel专用代码。

初始化Sentinel状态

Sentinel启动后,服务器会初始化一个sentinelState结构(即Sentinel状态),里面保存所有和Sentinel功能相关的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struce sentinelState{
uint 64_t current_epoch;

//保存了所有被这个sentine监视的主服务器
//字典的键是主服务器的名字
//字典的值则是一个指向 sentinelRedisInstance结构的指针
dict* masters;
//是否进入了TLT模式?
int tilt:
//目前正在执行的脚本的数量
int running_scripts:
//进入TILT模式的时间
mstime_t tilt_start_time:
//最后一次执行时间处理器的时间
mistime_t previous_time;
//一个FIFO队列,包含了所有需要执行的用户脚本
list *scripts_queue;
} sentinel;

初始化master属性

Sentinel状态中的masters字典记录了所有被 entinel监视的主服务器的相关信息,其中:

  • 字典的键是被监视主服务器的名字。
  • 字典的值则是被监视主服务器对应的sentinelRedisInstance结构。

每个 sentinelRedisInstance结构(后面简称“实例结构”)代表一个被 Sentinel监视的Redis服务器实例( Instance),这个实例可以是主服务器、从服务器,或者另外一个Sentinel

masters字典的初始化是根据被载入的 Sentinel配置文件来进行的

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
36
37
38
39
40
typedef struct sentinelRedisInstance{
//标识值,记录了实例的类型,以及该实例的当前状态
int flags;

//实例的名字
//主服务器的名字由用户在配置文件中设置
//从服务器以及 Sentinel的名字由 Sentinel自动设置
//格式为ip:port,例如"127.0.0.1:26379
char *name;

//实例的运行ID
char *runid;

//配置纪元,用于实现故障转移
uint64_t config_epoch

//实例的地址
sentinelAddr *addr;
// SENTINEL down-after-milliseconds选项设定的值
//实例无响应多少毫秒之后才会被判断为主观下线
mstime_t down_after_period;

// SENTINEL monitor< master-name> <IP> <port> < quorum> 选项中的 quorum参数
//判断这个实例为客观下线所需的支持投票数量
int quorum;
// SENTINEL parallel-syncs<master-name> < number>选 项的值
//在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
int parallel syncs;
// SENTINEL failover-timeout< master-name> <ms>选项的 值
//刷新故障迁移状态的最大时限
mtime_t failover_timeout;

//...
} sentinelRedisInstance;

//保存实例的ip和端口号
typedef struct sentinelAddr {
char *ip;
int port;
} sentinelAddr;

连接主服务器

初始化 Sentinel的最后一步是创建连向被监视主服务器的网络连接, Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息。

对于每个被Sentinel监视的主服务器来说, Sentinel会创建两个连向主服务器的异步网络连接:

  1. 命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复
  2. 另一个是订阅连接,这个连接专门用于订阅主服务器_sentinel_:hello频道

为什么需要两个连接?
因为既要订阅消息又要发送命令,redis无法在一个连接里面同时实现这两个功能。

获取主服务器信息

Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。

主要获取的信息如下:

  1. 主服务的run_id和role域记录的信息。(主服务器可能下线后变为从服务器,所以role域记录的是服务器当前的角色)。
  2. 主服务器下属所有从服务器的信息。(自动发现从服务器)

获取从服务器信息

当 Sentinel发现主服务器有新的从服务器出现时, Sentinel除了会为这个新的从服务器创建相应的实例结构之外, Sentinel还会创建连接到从服务器的命令连接和订阅连接。

在创建命令连接之后, Sentinel在默认情况下,会以每十秒一次的频率通过命令连接向从服务器发送INFO命令,获取从服务器信息。

  1. 从服务器的运行ID run_id。
  2. 从服务器的角色role
  3. 主服务器的IP地址 master_host,以及主服务器的端口号 master_port。
  4. 主从服务器的连接状态 master_link_status
  5. 从服务器的优先级slave_priority
  6. 从服务器的复制偏移量 slave_repl_offset

向主服务器和从服务器发送信息

在默认情况下, Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:

1
PUBLISH _sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>, <m_epoch>
  1. 以s开头的参数记录的是 Sentinel本身的信息
  2. m开头的参数记录的则是主服务器的信息
    • 如果Sentinel正在监视的是主服务器,那么这些参数记录的就是主服务器的信息;
    • 如果 Sentinel正在监视的是从服务器,那么这些参数记录的就是从服务器正在复制的主服务器的信息

接收主服务器和从服务器的频道信息

所有Sentinel都会订阅服务器的_sentinel_:hello频道,接收其他Sentinel发出的信息。

  1. 如果这个消息是自己发送的,直接丢弃
  2. 如果是别的Sentinel发送的,接收消息的Sentinel会根据信息中心的各个参数,对主服务器实例结构进行更新,同时更新这个主服务器的sentinels字典。(自动发现 Sentinel)

更新sentinels字典

创建连向其他Sentinel的命令连接

当 Sentinel通过频道信息发现一个新的 Sentinel时,它不仅会为新 Sentinel在 sentinels字典中创建相应的实例结构,还会创建一个连向新 Sentinel
的命令连接,而新 Sentinel也同样会创建连向这个Sentinel的命令连接,最终监视同一主服务器的多个 Sentinel将形成相互连接的网络。

检测主观下线状态

在默认情况下, Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括
主服务器、从服务器、其他 Sentinel在内)发送PING命令

如果一个实例在down-after-milliseconds毫秒内,连续向 Sentinel返回无效回复,那么 Sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,
此来表示这个实例已经进入主观下线状态。

down-after-milliseconds 作用范围

用户设置的down-after-milliseconds选项的值,不仅会被Sentinel用来判断
主服务器的主观下线状态,还会被用于判断主服务器属下的所有从服务器,以及所有同样监视这个主服务器的其他Sentinel的主观下线状态。

多个Sentinel设置的主观下线时长可能不同

如果有两个Sentinel同时监控一台master,s1载入的配置down-after-milliseconds为5000,s2载入的配置为10000,这样就可能导致s1认为master已经下线,s2认为master仍然在线。

检查客观下线状态

当 Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他 Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或者客观下线)。当 Sentinel从其他 Sentinel那里接收到足够数量的已下线判断之后, Sentinel就会将从服务器判定为客观下线,并对主服务器执行故障转移操作。

发送SENTINEL is-master-down-by-adr命令

Sentinel使用

1
SENTINEL is-master-down-by-addr <ip> <port> <current epoch> <runid>

命令询问其他 Sentinel是否同意主服务器已下线。

接收SENTINEL is-master-down-by-addr命令

当一个 Sentinel(目标 Sentinel)接收到另一个 Sentinel(源 Sentinel)发来的 SENTINEL is-master-down-by命令时,目标 Sentinel会分析并取出命令请求中包含的各个参数,并根据其中的主服务器IP和端口号,检查主服务器是否已下线,然后向源 Sentinel返回条包含三个参数的 Multi Bulk回复:

  1. <down state> 返回目标Sentinel对主服务器的检查结果,1代表主服务器已下线,0代表主服务器未下线
  2. <leader runid> 可以是*符号或者目标Sentine的局部领头Sentinel的运行ID
    • *符号代表命令仅仅用于检测主服务器的下线状态
    • 局部领头Sentinel的运行ID则用于选举领头Sentinel
  3. <leader epoch>局部领头Sentinel的配置纪元,用于选举领头Sentinel
    • 仅在leader runid的值不为*时有效
    • 如果 leader runid的值为*,那么leader epoch总为0

接收 is-master-down-by-addr命令的回复

Sentinel将统计其他 Sentinel同意主服务器已下线的数量,当这一数量达到配置指定的判断客观下线所需的数量时, Sentinel会将主服务器实例结构 flags属性的 SRI_O_DOWN标识打开,表示主服务器已经进入客观下线状态

客观下线的判断条件

当认为主服务器已经进入下线状态的Sentinel的数量,超过 Sentinel配置中设置的quorum参数的值,那么该 Sentinel就会认为主服务器已经进入客观下线状态。

1
2
sentinel monitor master 127.0.0.1 6379 2
//2 为quorum

不同的Sentinel判断客观下线的条件可能不同

Sentinel启动时载入的配置文件中的quorum不同,可能会导致不同的Sentinel判断客观下线的条件可能不同,参考多个Sentinel设置的主观下线时长可能不同

选举领头Sentinel

  1. 所有在线的Sentinel都有可能成为领头Sentinel
  2. 每次进行领头 Sentinel选举之后,不论选举是否成功,所有 Sentinel的配置纪元
    configuration epoch)的值都会自增一次。
  3. 在一个配置纪元里面,所有 Sentinel都有一次将某个 Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元里面就不能再更改。
  4. Sentinel设置局部领头 Sentinel的规则是先到先得:最先向目标Sentinel发送设置要
    求的源 Sentinel将成为目标 Sentinel的局部领头 Sentinel,而之后接收到的所有设置
    要求都会被目标 Sentinel拒绝。
  5. 因为领头 Sentinel的产生需要半数以上 Sentinel的支持,并且每个 Sentinel在每个配置纪元里面只能设置一次局部领头 Sentinel,所以在一个配置纪元里面,只会出现一个领头 Sentinel
  6. 如果在给定时限内,没有一个 Sentinel被选举为领头Sentinel,那么各个Sentinel将
    在一段时间(设定的故障迁移超时时间的两倍)之后再次进行选举,直到选出领头 Sentinel为止。

选举过程如下:

  1. 当一个Sentinel(源Sentinel)发现主服务器进入客观下线状态,它会要求其他Sentinel将自己设置为局部领头Sentinel
  2. 源Sentinel向目标Sentinel发送is-master-down-by-addr命令,要求目标Sentinel将源S设置为局部领头Sentinel
  3. 目标S收到命令后,进行回复,回复中的leader runid参数和leader epoch
    参数分别记录了目标S的局部领头Sentinel的运行ID和配置纪元。
  4. 源S在接收到目标S返回的命令回复之后,
    1. 检查回复中leader epoch参数的值和自己的配置纪元是否相同,(不相同直接过滤,配置版本落后)
    2. 如果配置纪元相同,源S继续取出leader runid参数,如果leader runid参数的值和源S的运行ID一致,那么表示目标S将源S设置成了局部领头Sentinel。
  5. 如果有某个Sentinel被半数以上(majority)的Sentinel设置成了局部领头Sentinel,那么这个Sentinel成为领头 Sentinel。

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

一个 Sentinel 都需要获得系统中**多数(majority)**Sentinel 的支持, 才能发起一次自动故障迁移, 并预留一个给定的配置纪元 (configuration Epoch ,一个配置纪元就是一个新主服务器配置的版本号)。

1
majority = num(sentinel)/2+1

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

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

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

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

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

故障转移

一次故障转移操作由以下步骤组成:

  1. 发现主服务器已经进入客观下线状态。
  2. 对我们的当前纪元进行自增(详情请参考 Raft leader election ), 并尝试在这个纪元中当选。
  3. 如果当选失败, 那么在设定的故障迁移超时时间的两倍之后, 重新尝试当选。 如果当选成功, 那么执行以下步骤。
  4. 选出一个从服务器,并将它升级为主服务器。
  5. 向被选中的从服务器发送 SLAVEOF NO ONE 命令,让它转变为主服务器。
  6. 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。
  7. 向已下线主服务器的从服务器发送 SLAVEOF host port 命令, 让它们去复制新的主服务器。
  8. 当所有从服务器都已经开始复制新的主服务器时, 领头 Sentinel 终止这次故障迁移操作。

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

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

其他

TILT 模式

Redis Sentinel 严重依赖计算机的时间功能: 比如说, 为了判断一个实例是否可用, Sentinel 会记录这个实例最后一次相应 PING 命令的时间, 并将这个时间和当前时间进行对比, 从而知道这个实例有多长时间没有和 Sentinel 进行任何成功通讯。

TILT 模式是一种特殊的保护模式: 当 Sentinel 发现系统有些不对劲时, Sentinel 就会进入 TILT 模式。

  • 如果两次调用时间之间的差距为负值, 或者非常大(超过 2 秒钟), 那么 Sentinel 进入 TILT 模式。
  • 如果 Sentinel 已经进入 TILT 模式, 那么 Sentinel 延迟退出 TILT 模式的时间。

当 Sentinel 进入 TILT 模式时, 它仍然会继续监视所有目标, 但是:

  • 它不再执行任何操作,比如故障转移。
  • 当有实例向这个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令时, Sentinel 返回负值: 因为这个 Sentinel 所进行的下线判断已经不再准确。