【学习Redis系列】Redis之Sentinel(哨兵)

前言:

Sentinel是Redis高可用性解决方案,何为高可用,即主从两个Redis服务器,若主服务器挂了,通过Sentinel能切换到从服务器进行服务,不需要人为的切换。Sentinel实例组成的系统可以监视任意多个主服务器,当某个主服务器下线后,可以从这个主服务器下的从服务器中挑选出一个从服务器升级为新的主服务器,进行故障转移。

1.Sentinel的初始化:

Sentinel是一种特殊的redis服务器,Sentinel与redisServer的区别大致体现在下面几点 :

  1. Sentinel不需要使用数据库,即不需要启动不需要加载RDB或AOF文件
  2. Sentinel与redisServer使用不同的command列表,决定了Sentinel可执行的命令与redisServer并不相同
  3. Sentinel实例中,serverCron会调用sentinelTimer函数执行关于sentinel的相关事件

一个Sentinel的启动流程可以大概总结为:

  1. 初始化服务器
  2. 将普通的redis服务器使用的代码替换为Sentinel代码
  3. 初始化Sentinel状态
  4. 根据配置文件中内容,初始化Sentinel的监视服务器列表
  5. 创建与主服务器的网络连接

结合代码观察下启动流程:在main函数中调用checkForSentinelMode(int argc, char **argv)函数判断这个redis服务器是否按照sentinel参数启动,代码如下,也同时看出可用两种命令方式启动Sentinel

第一种:redis-sentinel  sentinel.conf

第二种:redis-server    sentinel.conf  --sentinel

函数返回值会保存在sentinel_mode中,server.sentinel_mode = checkForSentinelMode(argc,argv); 

server.sentinel_mode为true时,调用initSentinel函数。代码如下

扫描二维码关注公众号,回复: 9042738 查看本文章

下图可看出sentinelcmds 与 redisCommandTable 命令大不相同。


2.Sentinel中保存的主服务器

Sentinel中masters字典保存了所有被Sentinel监视的主服务器,字典的键是监视服务器的名字,值是被监视的服务器对应的sentinelRedisInstance结构。

Tips:每个sentinelRedisInstance实例可以是一个主服务器,从服务器或者一个Sentinel这三种类型其一。

观察下图sentinelRedisInstance的部分成员

通过一个加载sentinel.conf配置,如图 ,创建一个sentinelRedisInstance实例。如图1-1(master2并未在写入配置,1-1中的master2只是表示:一个sentinel可监视多个主服务器。)

初始化sentinel的最后一步是创建与主服务器的连接。会创建两条连接

  1. 一条用于与主服务器发送命令,接收回复
  2. 另一条用于订阅__sentinel__:hello频道(通过这个频道获取其余监视这个主服务器的sentinel信息,交换彼此信息。)

3.获取主服务器信息

sentinel以每10秒一次的频率通过命令连接向被监视的主服务器发送INFO命令。通过分析INFO命令的回复获取主服务器的当前信息。

通过INFO命令,获取主服务器的信息:

  1. 获取服务器的run_id域记录的服务器运行ID,role域记录的服务器角色
  2. 记录主服务器下的所有从服务器信息。

我启动了三个redis服务器,port 6379为主服务器,其余18181,12345为从服务器,则sentinel获取从服务器信息如下:

sentinel known-slave master1 127.0.0.1 18181

sentinel known-slave master1 127.0.0.1 12345,

sentinel根据获取的从服务器信息:

1.创建从服务器的实例保存到主服务器实例中的slaves字典中,如图1-1。

2.更新slaves字典中存在的slave实例对象。

从服务器实例结构flags值为SRI_SLAVE,而主服务器为SRI_MASTER;从服务器实例name属性为IP:PORT组成,主服务器为sentinel配置文件设置,即conf中的master1。

4.获取从服务器信息

 sentinel获取到从服务器信息后,除了为从服务器创建实例,也会与从服务器建立两个网络连接。并且连接建立后,每10s发送一个INFO命令。根据命令回复,获取以下信息:

  • 从服务器的运行run_id
  • 从服务器的角色role
  • 主服务器的ip port
  • 主服务器的连接状态
  • 从服务器的优先级
  • 从服务器的复制偏移量

根据这些信息对从服务器的信息进行更新。

5.获取其余Sentinel的信息

Sentinel会以接近于(为何说接近,后文有写)2s一次的频率调用sentinelSendHello函数向主从服务器发送以下格式命令:

PUBLISH __sentinel__:hello <s_ip><s_port><s_uid><s_epoch><m_name><m_ip><m_port><m_epoch>

之前说Sentinel与主从服务器都会建立两条连接,其中订阅连接就会订阅__sentinel__:hello频道,即通过命令连接像服务器的__sentinel__:hello频道发送消息,也通过订阅频道接收消息。

对于监视同一个服务器的多个sentinel,一个sentinel发送的消息会被其他的sentinel通过__sentinel__:hello频道接收到,更新其他sentinel对于发送sentinel的认知,也会更新其他sentinel对于服务器的认知。即图1-1中的dict *sentinels,记录了出本sentinel外其余监视这个服务器的sentinel结构信息。

那么上述2s一次的调用从代码中如何体现?

具体函数调用过程如下

  • serverCron -> sentinelTimer
  • sentinelTimer -> sentinelHandleDictOfRedisInstances  
  • sentinelHandleDictOfRedisInstances  -> sentinelHandleRedisInstance  
  • sentinelHandleRedisInstance   -> sentinelSendPeriodicCommands  
  • sentinelSendPeriodicCommands  ->  sentinelSendHello

sentinelSendPeriodicCommands  函数中关于 sentinelSendHello的代码如下,ri是sentinelRedisInstance实例,这个实例中的last_pub_time记录了上次发送命令的时间,SENTINEL_PUBLISH_PERIOD 是一个宏定义#define SENTINEL_PUBLISH_PERIOD 2000,所以当前时间大于距离上次发送命令时间>2时,则再次发送命令。这便说明了为何说接近2s的时间发送一次。

6.创建与其他sentinel的命令连接

通过订阅频道获取到监视同一服务器的其他sentinel信息时,如图1-1中 dict * sentinels 字典所示,创建 或 更新其他sentinel实例。当发现一个新的sentinel创建相应实例结构后,还会与其创建一个命令连接,而这个新的sentinel也会创建一个命令连接到这个sentinel 。即两个sentinel之间存在两条连接,各自创建一条。正式因为这个命令连接互相交换消息,才实现了sentinel主观下线检测, 客观下线检测,以及sentinel选举 和最终目的故障转移,当主服务器挂了的时候,能够继续使用从服务器服务,并将从无服务器升级为主服务器。

7.主观下线检测

sentinel会以1s一次的频率向所有与它创建了命令连接的sentinelRedisInstance实例对象(即 主服务器 从服务器 其他sentinel)发送PING消息,sentinel实例中的down_after_millisecons指定了sentinel判断实例进入主观下线的时间长度,如果down_after_millisecons毫秒内,未收到有效回复,则sentinel会修改这个实例,将实例结构中的flags属性中的SRI_S_DOWN标识打开。

8.客观下线检测

8.1发送主观下线消息

当一个sentinel将主服务器判断为主观下线后,会调用函数 sentinelAskMasterStateToOtherSentinels,根据函数名字,也可以看出询问其他sentinel这个主服务器状态如何。这个函数会发送命令is-master-down-by-addr,其参数为主机ip,端口port,当前sentinel的配置纪元,第四个参数是一个条件表达式:

(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?sentinel.myid : "*", 如果这个sentinel开启了故障转移状态则为sentinel的id,否则为 '*'。在客观下线检测时,发送的则是'*'。

命令 SENTINEL is-master-down-by-addr 127.0.0.1 6379 0 * 表示当前主服务器IP为127.0.0.1 端口6379的主服务器被判断为主观下线。

8.2 回复主观下线消息

接收到这个命令的其余sentinel根据这个命令中的参数,检测服务器是否下线,并发送回复SENTINEL is-master-down-by-addr <state> <leader_runid><leader_epoch>

  • leader_runid     为 '*'表示仅仅用于检测服务器下线状态,否则为选举的领头sentinel id
  • leader_epoch   用于选举领头sentinel使用,参与投票这个sentinel的当前配置纪元
  • state            对于主服务器的检查结果,1代表下线 0代表未下线。

8.3 接收消息回复

根据其他sentinel返回的消息,如果认为服务器下线的数量超过该sentinel配置中quorum的值,那么该sentinel就认为主服务器进入客观下线状态,打开SRI_O_DOWN标识。

需注意,不同的sentinel配置判断客观下线的条件可能不同,一个sentinel认为客观下线时,未必达到其他sentinel配置的客观下线条件

比如 sentinel master monitor 127.0.0.1 6379 2 与 sentinel master monitor 127.0.0.1 6379 5,一个quorum值为2 一个为5,即一个认为有两个sentinel判断服务器下线,则可以判定为客观下线;另一个认为至少有5个sentinel判断服务器下线,才可判定为客观下线。

9.选举领头sentinel

当sentinel判断服务器客观下线后,就会进行故障转移,进行故障转移之前,要先选举出一个零头sentinel,有这个领头sentinel进行故障转移操作。不然10个sentinel监视,10个都执行故障转移,岂不是乱套了。

 选举的规则和方法如下:

  • 首先明确所有的sentinel都有选举资格,看谁网速快,看谁先动手。
  • 每次选举之后,即1号sentinel选手给2号sentinel选手投了票,那1号选手的配置纪元加1,比如此时配置为8,表示第8轮投票我已经参与过了,再接收到3号的投票要求时,如果还是第8轮,则不在投票
  • 每个配置纪元中,每个sentinel只有一次的投票机会
  • 每个发现主服务器客观下线的sentinel都会要求其他sentinel为他投票
  • 当发送的SENTINEL is-master-down-by-addr中runid参数不是 * 而是sentinel的runid时,表示源sentinel要求目标sentinel将回复中的leader_runid填写为自己,并且记录此轮配置纪元
  • 当源sentinel收到回复is-master-down-by-addr时,先检测配置纪元是否相同,相同在去除leader_runid与自己的runid比较,相同则表示自己又得一票
  • 当某个sentinel得票超过一半之后,则当选领头sentinel
  • 如果给定时间内,没有选出领头sentinel ,则一段时间后重新一轮选举。

10.故障转移

选出领头sentinel后,将对下线的主服务器进行故障转移。

  1. 在下线的主服务器的从服务器中,挑选一个从服务器,让其升级为主服务器
  2. 让下线的主服务器属下的从服务器改为复制新的主服务器
  3. 将下线的主服务器设置为新的主服务器的从服务器

10.1 挑选新的主服务器

将挑选出的状态良好,数据完整的从服务器,发送slave no one命令,将其转为主服务器。发送slave no one 命令后,以每1s一次的频率向被升级从服务器发送INFO命令,观察其身份变化。

挑选的规则如下: 现将已下线的主服务器的从服务器保存到一个列表中

  1. 从列表中删除处于下线状态的从服务器
  2. 从列表中删除最近5s内没有回复领头sentinel的INFO命令
  3. 删除与已下线主服务器连接断开时间超过down-after-milliseconds * 10的从服务器,保证剩余的从服务器没有过早的与主服务器断开,即数据相对较新
  4. 选择优先级较高的从服务器
  5. 若从服务器优先级相同,选择偏移量较大的
  6. 若优先级相同,偏移量相同,将按照运行ID排序,选出ID最小的从服务器

10.2 修改从服务器的复制目标

当挑选出新的主服务器后,将旧服务器中的从服务器全部执行SLAVEOF <ip><port> 拷贝这个新的主服务器

10.3 将旧的主服务器设置为从服务器

在旧的主服务器中保存设置,当其重新上线时,执行SLAVEOF命令,称为从服务器

猜你喜欢

转载自www.cnblogs.com/smallhehe/p/12275056.html