Redis高可用——哨兵Sentinel

591 阅读13分钟

前言

本文将对Redis的高可用方案———哨兵模式进行介绍,讲解其内部实现原理。

概述

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

如上图为一个 Sentinel系统实例:

  • server1 为当前的主服务器
  • server2、server3、server4 为主服务器的三个从服务器
  • 三个从服务器正在复制主服务器,而 Sentinel 系统则在监视所有服务器

假设这时,主服务器 server1 进入下线状态,那么三个从服务器对主服务器的复制操作将被终止,并且 Sentinel 系统会察觉到 server1 已下线。

当主服务器server1的下线时长超过用户设定的下线时长上限时,Sentinel系统会对 server1 执行故障转移操作:

  • 首先,Sentinel 系统会挑选server1属下的其中一个从服务器,并将这个被选中的从服务器升级为新的主服务器
  • 之后,Sentinel 系统会向server1属下的所有从服务器发送新的复制指令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。
  • 另外,Sentinel 还会继续监视已下线的server1,并在它重新上线时,将它设置为新的主服务器的从服务器。

启动及初始化Sentinel

启动一个Sentinel实例可使用以下命令其一即可:

  • redis-sentinel /etc/redis/sentinel.conf
  • redis-server /etc/redis/sentinel.conf

当一个 Sentinel实例启动时,需要执行以下步骤:

  • 初始化服务器
  • 将普通Redis服务器使用的代码替换成Sentinel专用代码
  • 初始化Sentinel状态
  • 根据给定的配置文件,初始化Sentinel的监视主服务器列表
  • 创建连向主服务器的网络连接(网络连接又包括:命令连接、订阅连接)

哨兵原理

获取主服务器信息

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

通过 INFO 命令回复,Sentinel可以获取以下两方面的信息:

  • 一方面是关于主服务器本身的信息,包括 run_id域记录的服务器运行 ID,以及 role域记录的服务器角色
  • 另一方面是关于主服务器属下所有从服务器的信息,每个从服务器都由一个 slave字符串开头的行记录,Sentinel与主从服务器的通信,每行的ip=域记录了从服务器的地址,而port=域则记录了从服务器的端口号。根据这些IP地址和端口号,Sentinel无须用户提供从服务器的地址信息,就可以自动发现从服务器。

至于主服务器返回的从服务器信息,则会被用于更新主服务器实例结构的slaves字典,这个字典记录了主服务器属下从服务器的名单:

Sentinel在分析INFO命令中包含的从服务器信息时,会检查从服务器对应的实例结构是否已经存在于slaves字典:

  • 如果从服务器对应的实例结构已经存在,那么Sentinel对从服务器的实例结构进行更新。
  • 如果从服务器对应的实例结构不存在,那么说明这个从服务器是新发现的从服务器,Sentinel会在slaves字典中为这个从服务器新创建一个实例结构。

对于一个master和三个slave的例子来说,Sentinel将分别为三个从服务器创建他们各自的实例结构,并将这些结构保存到主服务器实例结构的 slaves字典里,如图所示。

image.png

图中主服务器实例结构和从服务器实例结构之间的区别:

  • 主服务器实例结构的flags属性的值为SRI_MASTER,而从服务器实例结构的flags属性的值为SRI_SLAVE
  • 主服务器实例结构的name属性的值是用户使用Sentinel配置文件设置的,而从服务器实例结构的name属性的值则是Sentinel根据从服务器的IP地址和端口号自动设置的

获取从服务器信息

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

如图所示,Sentinel与各个从服务器建立命令连接和订阅连接。

image.png

向所有主从服务器发送信息

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

PUBLISH __sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"

信息的内容由多个参数组成:

  • s_ip:Sentinel的IP地址
  • s_port:Sentinel的Port
  • s_runid:Sentinel的运行ID
  • s_epoch:Sentinel当前的配置纪元
  • m_name:主服务器名称
  • m_ip:主服务器IP地址
  • m_port:主服务器Port
  • m_epoch:主服务器当前的配置纪元

接收来自主从服务器的信息

Sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令:

SUBSCRIBE_sentinel_:hello

Sentinel对频道的订阅会持续到 Sentinel 与服务器的连接断开为止。

这也就是说,对于每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的sentinel_:hello频道发送信息,又通过订阅连接从服务器的_sentinel_:hello频道接收信息,如图所示。

image.png

对于监视同一个服务器的多个Sentinel来说,一个Sentinel 发送的信息会被其他Sentinel接收到,这些信息会被用于更新其他Sentinel对发送信息Sentinel 的认知,也会被用于更新其他Sentinel对被监视服务器的认知。

假设现在有三个 Sentinel在监视同一个服务器,那么当服务器Sentinel1向频道发送一条信息时,所有订阅了此频道的Sentinel都会收到这条信息。如下图所示。

image.png

检测主观下线状态

在默认情况下,Sentinel 会以每秒一次的频率向所有与它创建了命令连接的实例包括主服务器、从服务器、其他Sentinel在内发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。

其中,有效回复

  • +PONG
  • +LOADING
  • -MASTERDOWN

无效回复为除了有效的三种回复外的其他回复。

Sentinel配置文件中的down-after-milliseconds选项指定了Sentinel判断实例进入主观下线所需的时间长度:如果一个实例在down-after-milliseconds毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来表示这个实例已经进入主观下线状态。

检测客观下线状态

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

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

发送命令

Sentinel使用 SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid> 命令询问其他 Sentinel是否同意主服务器已下线,命令中的各参数:

  • ip:被Sentinel判断为主观下线的主服务器的IP地址
  • port:被Sentinel判断为主观下线的主服务器的端口号
  • current_epoch:Sentinel当前的配置纪元,用于选举领头Sentinel
  • runid:可以是*符号或者Sentinel的运行ID,*符号代表命令仅仅用于检测主服务器的客观下线状态,而Sentinel的运行ID则用于选举领头Sentinel

接收命令

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

  • <down_state>:返回目标Sentinel对主服务器的检查结果,1:已下线,0:未下线
  • <leader_runid>:可以是*符号或者目标Sentinel的局部领头Sentinel的运行ID
  • <leader_epoch>:目标Sentinel的局部领头Sentinel的配置纪元,用于选举领头 Sentinel,如果<leader_runid>为*,那么<leader_epoch>总为0

接受命令回复

根据其他Sentinel 发回的SENTINEL is-master-down-by-addr命令回复,Sentinel将统计其他Sentinel同意主服务器已下线的数量,当这一数量达到配置(Sentinel配置中的设置的quorum参数值)指定的判断客观下线所需的数量时,Sentinel会将主服务器实例结构flags属性的SRI_O_DOwN标识打开,表示主服务器已经进入客观下线状态。

对于监视同一个主服务器的多个Sentinel来说,它们将主服务器判断为客观下线的条件可能也不同:当一个Sentinel将主服务器判断为客观下线时,其他Sentinel可能并不是那么认为的。比如说,对于监视同一个主服务器的五个Sentinel来说,如果Sentinel1在启动时载入了以下配置:

sentinel monitor master 127.0.0.1 6379 2

那么当五个 Sentinel中有两个 Sentinel认为主服务器已经下线时,Sentinel1就会将主服务器判断为客观下线。

而对于载入了以下配置的 Sentinel2来说:

sentinel monitor master 127.0.0.1 6379 5

仅有两个Sentinel认为主服务器已下线,并不会令 Sentinel2将主服务器判断为客观下线。

选举领头Sentinel

当一个主服务器被判断为客观下线后,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。

Redis是如何选举出领头Sentinel的?

所有在线的Sentinel都有被选为领头Sentinel的资格,如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么这个Sentinel称为领头Sentinel,例如,在一个由10个Sentinel组成的Sentinel系统里,只要有大于等于 10/2+1=6个Sentinel将某个Sentinel设置为局部领头Sentinel,那么被设置的那个Sentinel就会称为领头Sentinel

每次进行选举后,不论选举是否成功,所有Sentinel的配置纪元的值都会自增一次,实际上,配置纪元只是一个计数器。

如果在给定时限里,没有一个Sentinel被选举为领头Sentinel,那么各个Sentinel将在一段时间之后再次进行选举,直到选出领头Sentinel为止。

故障转移

在选举产生出领头Sentinel之后,领头Sentinel将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:

1)在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。

2)让已下线主服务器属下的所有从服务器改为复制新的主服务器。

3)将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。

选举出新的主服务器

故障转移第一步就是要在已下线主服务器属下的所有从服务器中,挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送 SLAVEOF no one命令,将这个从服务器转换为主服务器。

在一次故障转移操作中,领头 Sentinel向被选中的从服务器server2发送SLAVEOF no one命令, 在发送SLAVEOF no one命令之后,领头Sentinel会以每秒一次的频率(平时是每十秒一次),向被升级的从服务器发送INFO命令,并观察命令回复中的角色(role)信息,当被升级服务器的role从原来的slave变为master时,领头Sentinel就知道被选中的从服务器已经顺利升级为主服务器了。

image.png

修改从服务器的复制目标

当新的主服务器出现之后,领头Sentinel下一步要做的就是,让已下线主服务器属下的所有从服务器去复制新的主服务器,这一动作可以通过向从服务器发送SLAVEOF命令来实现。

展示了在故障转移操作中,领头Sentinel向已下线主服务器serverl的两个从服务器server3和server4发送SLAVEOF命令,让它们复制新的主服务器server2的例子。

image.png

将旧的主服务器变为新从服务器

故障转移最后一步,将已下线的主服务器设置为新的主服务器的从服务器。

因为旧的主服务器已经下线,所以这种设置是保存在 server1 对应的实例结构里的,当 server1 重新上线后,Sentinel会向它发送 SLAVEOF命令,让它成为 server2 的从服务器。

image.png

小结

哨兵会定时执行下面3个操作:

  • Sentinel以每10秒一次的频率向被监视的主从服务器发送INFO命令,当主服务器处于下线状态,或者Sentinel正在对主服务器进行转移时,Sentinel向从服务器发送INFO命令的频率会改为每秒一次。
  • Sentinel每2秒向主数据库和从数据库的_sentinel_:hello 频道发送自己的信息
  • Sentinel每秒会向主数据库、从数据库和其他哨兵节点发送PING命令

本文主要介绍了Redis的高可用方案之一:Sentinel模式。如对Redis感兴趣,可继续关注本专栏。