REDIS设计与实现之sentinel


    redis主从复制可将主节点数据同步给从节点,一旦主节点宕机,从节点作为主节点的备份可以随时顶上来。这个过程如果人工介入,效果肯定没有自动的高可用机制好。sentinel 哨兵机制就是为了解决这个问题。是redis的高可用HA方案:有一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

  

启动并初始化sentinel
 启动一个 Sentinel 可以使用命令:

$ redis-sentinel /path/to/your/sentinel.conf
$ redis-server /path/to/your/sentinel.conf --sentinel
这两个命令都能启动Sentinel,效果都是一样的。

Sentinel启动后,会有五个步骤:

初始化服务器。
将普通 Redis 服务器使用的代码替换成 Sentinel 专用代码。
初始化 Sentinel 状态。
根据给定的配置文件, 初始化 Sentinel 的监视主服务器列表。
创建连向主服务器的网络连接。
下面分别展开。

初始化服务器
Sentinel的本质是一个运行在特殊模式下的Redis服务器,因此启动时必须对其进行初始化,但是由于Sentinel与普通的服务器不同,它的初始化需要执行的操作也不同。


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

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

命令连接,用于向主服务器发送命令,并接收命令回复
订阅连接,用于订阅主服务器的 __sentinel__:hello 频道

为什么有两个连接?

在 Redis 目前的发布与订阅功能中, 被发送的信息都不会保存在 Redis 服务器里面, 如果在信息发送时, 想要接收信息的客户端不在线或者断线, 那么这个客户端就会丢失这条信息。

因此, 为了不丢失 __sentinel__:hello 频道的任何信息, Sentinel 必须专门用一个订阅连接来接收该频道的信息。

而另一方面, 除了订阅频道之外, Sentinel 还又必须向主服务器发送命令, 以此来与主服务器进行通讯, 所以 Sentinel 还必须向主服务器创建命令连接。

并且因为 Sentinel 需要与多个实例创建多个网络连接, 所以 Sentinel 使用的是异步连接。

接下来的一节将介绍 Sentinel 是如何通过命令连接和订阅连接来与被监视主服务器进行通讯的。

 

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

关于服务器本身的信息
      包括 run_id 域记录的服务器运行ID,以及 role 域记录的服务器角色

关于主服务器属下的所有从服务器信息
        每个从服务器都由一个“slave”字符串开头的行记录,每行的 ip= 域记录了从服务器的IP地址, port= 域记录了从服务器的端口号。根据这些IP地址和端口号,Sentinel无须用户提供从服务器的地址信息,就可以自动发现从服务器。

   根据 run_id 域和 role 域的信息,Sentinel将对主服务器的实例结构进行更新。而主服务器返回的从服务器信息,将会被用于更新主服务器实例结构的 slaves 字典(记录了属下从服务器的名单)。

   Sentinel 分析 INFO 命令中包含的从服务器信息时,会检查从服务器对应的实例结构是否已经存在于 slaves 字典: 如果存在,就对从服务器的实例结构进行更新,如果不存在(表明这个从服务器是新发现的从服务器),Sentinel会在 slaves 字典中为这个从服务器创建一个新的实例结构。

书上给了图,这里略过。注:

主服务器实例结构的 flags 值为 SRI_MASTER,从服务器是 SRI_SLAVE
主服务器实例结构的 name 由用户使用Sentinel配置文件设置,从服务器的name 是由Sentinel根据服务器ip+port自动设置的
四  获取从服务器信息
  当Sentinel发现主服务器有新的服务器出现时,除了会为这个新从服务器创建相应的实例结构之外,还会创建连接到从服务器的命令连接和订阅连接。

创建了命令连接之后,每10秒一次向从服务器发送 INFO 命令,并根据回复分析以下信息:

从服务器的运行ID run_id
从服务器的角色 role
主服务器的ip地址 master_host 以及主服务器的端口号 master_port
主从服务器的连接状态 master_link_status
从服务器的优先级 slave_priority
从服务器的复制偏移量 slave_repl_offset
根据这些信息,Sentinel会对从服务器的实例结构进行更新。
五 向主服务器和从服务器发送信息
默认情况下,sentinel每两秒一次,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:

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

这条命令就表示向服务器的 __sentinel__:hello 频道发送一条信息,信息由一下部分组成:

以 s_ 开头的参数记录Sentinel本身的信息
以 m_ 开头的参数则是主服务器的信息,当然如果监视的是从服务器,这个信息表示的就是从服务器的信息
 

 接收来自主服务器和从服务器的频道信息
在建立起订阅连接之后,Sentinel会通过这个连接,向服务器发送以下命令:

SUBSCRIBE __sentinel__:hello

Sentinel对 __sentinel__:hello 这个定于会一直持续到Sentinel与服务器的连接断开之后。

就是说sentinel 可以通过这个频道发送和接收信息。

对于监视同一服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他的Sentinel接收到,并用于更新其他Sentinel对发送信息Sentinel的认知,和被用于更新其他Sentinel对被监视服务器的认知(收到自己发送的会被忽略掉)。

 更新sentinels字典
   sentinels 字典保存了除Sentinel本身之外,所有同样监视这个主服务器的其他Sentinel资料:

键 是其中一个Sentinel的名字,格式:ip:port
值 是对应Sentinel的实例结构
     当一个Sentinel接收到其他Sentinel发来的信息时(称发送信息的Sentinel为源Sentinel,接收信息的Sentinel为目标Sentinel),目标Sentinel会从信息中分析出以下信息:

     与Sentinel相关的参数:源Sentinel的IP、port、run_id、配置纪元
     与主服务器相关参数:源Sentinel 正在监视的主服务器的名字、IP、port、配置纪元
      根据这些主服务器参数,目标Sentinel会在自己的Sentinel状态的 masters 字典中查找相应的主服务器实例结构,然后根据提出的Sentinel参数,检查主服务器实例结构的 sentinels 字典中,源 Sentinel的实例结构是否存在:

存在,就对源Sentinel的实例结构进行更新
不存在,说明源Sentinel是才开始监视主服务器的新Sentinel,目标Sentinel会为源Sentinel创建一个新的实例结构,并将这个结构添加到 sentinels 字典里面
因为一个sentinel可以通过分析接受的频道信息知道其它sentinel的存在,也可以通过发送频道信息让其它sentinel知道自己的存在,所以用户不需要提供各个sentinel的地址监视同一主服务器的多个sentinel 可以自动发现对方。

创建连向其他Sentinel的命令连接
    当Sentinel通过频道信息发现了一个新的Sentinel时,它不仅会为新的Sentinel在 sentinels 字典中创建相应的实例结构,还会创建一个连向新Sentinel的命令连接。新的Sentinel同样会创建连向这个Sentinel的命令连接,最终监视同一主服务器的多个Sentinel将形成相互连接的网络:SentinelA有连向SentinelB的命令连接,SentinelB也有连向SentinelA的命令连接。
    使用命令连接的各个sentinel 可以通过向其它sentinel发送命令来进行信息交换。下面介绍的服务器监视下线就是使用sentinel之间的命令通信。

  Sentinel之间不会创建订阅连接:靠订阅主、从服务器的频道信息,已经能获取未知sentinel。而sentinel之间通信靠命令通信足够。

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

两种实例对 PING 指令的回复情况:

有效回复:实例返回 +PONG 、 -LOADING 、-MASTERDOWN 三种其中一种
无效回复,除了上面三种之外的其它回复,或者在指定时限内没有返回任何回复
Sentinel配置文件中的 down-after-millseconds 选项指定了Sentinel判断实例进入主观下线所需的时间长度:如果一个实例在 down-after-millseconds 毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例所对应的实例结构,在结构的 flags 属性中打开 SRI_S_DOWN 标识,用于表示这个实例已经进入主观下线状态。

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

2. 多个Sentinel设置的主观下线时长可能不同,对于监视同一个主服务器的多个Sentinel来说,这些Sentinel设置的 down-after-milliseconds 选项的值可能不同,因此,当一个Sentinel将主服务器判断为主观下线时,其它Sentinel可能任然会认为主服务器处于在线状态。

 八 检测客观下线
   当Sentinel将一个主服务器判断为主观下线之后,为确定这个服务器是否真的下线,它会向同样监视这个主服务器的其它Sentinel进行询问,当接收到足够数量的已下线判断之后,Sentinel就会将从服务器判定为客观下线,并对主服务器进行故障转移操作。

8.1. 发送 SENTINEL is-master-down-by-addr 命令
Sentinel会发送下面的命令询问其它Sentinel是否同意主服务器下线:

   SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>


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

<down_state>    返回目标Sentinel对主服务器的检查结果,1表示主服务器已下线,0表示主服务器未下线
<leader_runid>    可以是 * 符号或者目标Sentinel的局部领头Sentinel的运行ID,*表示命令仅仅用于检测主服务器的下线状态,
而局部领头Sentinel的运行ID则用于选举领头Sentinel
<leader_epoch>    目标Sentinel的局部领头Sentinel的配置纪元,用于选举领头Sentinel。
仅在 leader_runid 值不为 * 时有效,如果其值为 * ,这个参数总为0
8.3. 接收 SENTINEL is-master-down-by-addr 命令的回复
       根据其他Sentinel发回的 SENTINEL is-master-down-by-addr 回复,Sentinel将统计同意主服务器下线的数量,当这个值达到配置指定的判断客观下线所需的数量时(即 quorum 属性的值),Sentinel会将主服务器实例结构中 flags 属性的 SRI_O_DOWN 标识打开,标识主服务器已经进入客观下线状态。
注意:不同sentinel可能客观判断下线条件不一样。

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

如何选举领头Sentinel的规则和方法:

1.每个做主观下线的sentinel节点向其他sentinel节点发送上面那条命令,要求将它设置为领导者。

2.收到命令的sentinel节点如果还没有同意过其他的sentinel发送的命令(还未投过票),那么就会同意,否则拒绝。

3.如果该sentinel节点发现自己的票数已经过半且达到了quorum的值,就会成为领导者

4.如果这个过程出现多个sentinel成为领导者,则会等待一段时间重新选举。

十 故障转移
  在选举产生出领头Sentinel之后,领头Sentinel将对已下线的主服务器进行故障转移操作:

1在已下线的主服务器属下的所有从服务器中,挑选一个从服务器作为主服务器

挑选一个状态良好、数据完整的从服务器,然后发送 SLAVEOF no one 命令,然后将这个从服务器转换成主服务器

 删除列表中所有已经下线的或者短线状态的从服务器。(保证从服务器都是在线的)
 删除列表中最近5秒内没有回复过头sentinel的INFO命令的从服务器(保证从服务器都是最近通信过的)
 删除所有与已下线主服务器连接断开超过 阈值的从服务器,保证剩余从服务器数据都是比较新的。再看如何排序选择剩余的从服务器。
选择slave-priority(slave节点优先级配置)最高的slave节点,(默认都是一样的)例如:如果我们有两台slave在两台机器上,一台配置较高,我们希望当master挂掉优先选配置高的,就可以配置该值为slave中最高的。如果存在最高则返回,不存在继续
选择复制偏移量最大的节点(复制得最完整,与master节点的数据一致性更高),如果存在则返回,不存在继续
如果以上两个条件都不满足,选runId最小的(启动最早的)
2让已下线的主服务器的所有从服务器改为复制新的主服务器

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值