Redis哨兵机制 - Sentinel

Sentinel是Redis高可用性的解决方案,由一个或多个Sentinel实例组成的sentinel系统可以监视任意多个主服务器以及主服务器对应的从服务器,并在被监视的主服务器下线时自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求

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

  • 初始化服务器

    • 与普通服务器不同的是,在普通服务器初始化时会通过载入RDB或者AOF文件来恢复数据库状态,而Sentinel并不使用数据库,所以在Sentinel初始化时不会载入RDB文件或者AOF文件
  • 将普通Redis服务器使用的代码替换成Sentinel专用代码

  • 初始化Sentinel状态

  • 根据给定的配置文件初始化Sentinel的监视主服务器列表

  • 创建连向主服务器的网络连接

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

      • 命令连接:用于向主服务器发送命令并接受命令回复
      • 订阅连接:用于订阅主服务器的__sentinel__:hello频道
    • 为什么需要两个连接?

      • 在Redis 的发布订阅功能中,被发送的信息不会保存在Redis服务器里,如果在信息发送时,想要接收信息的客户端不在线或者断线,那么这个客户端就会丢失信息,因此为了不丢失信息,Sentinel必须用一个专门的订阅连接来接收来自此频道的信息
      • 除了订阅频道之外,sentinel还需要向主服务器发送命令,以此来与主服务器进行通信

      因此,Sentinel需要与多个实例创建多个网络连接,它使用的是异步连接

获取主服务器信息

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

  • 主服务器本身的信息:runId,role等,可以通过这些信息来对主服务器实例结构进行更新
  • 从服务器的信息:ip,port等,这些信息用于更新主服务器实例结构的slaves字典(记录了主服务器属下的从服务器名单),如果从服务器对应的实例结构已经存在与slaves字典中,那么只会对其进行更新,如果从服务器对应的实例结构不存在,那么说明这个从服务器是新发现的从服务器,sentinel会在slaves字典中为这个从服务器创建一个新的实例结构

获取从服务器信息

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

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

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

PUBLIC __sentinel__:hello "<sentinel_ip>,<sentinel_port>,<sentinel_runid>,<sentinel_epoch>,<master_name>,<master_ip>,<master_port>,<master_epoch>"

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

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

SUBSCRIBE __sentinel__:hello

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

更新sentinels字典

Sentinel为主服务器创建的实例结构中的sentinels字典保存了除Sentinel本身还有其他同样监视该服务器的Sentinel的资料,通过接收到Sentinel发送的信息,Sentinel会更新对应的实例解雇和sentinels字典

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

当Sentinel通过频道信息发现一个新的sentinel时,它不仅会为新的Sentinel在sentinels字典总创建对应的实例结构,还会创建一个连向新的Sentinel的命令了解,而新Sentinel同样会创建连上这个Sentinel的命令连接

检测主观下线状态

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

回复分为有效回复和无效回复:

  • 有效回复:+PONG、-LOADING、-MASTERDOWN其中一种
  • 无效回复:除有效回复之外的回复或者指定时间内没有回复

如果一个实例在down-after-milliseconds毫秒内,连续向Sentinel返回无效回复,那么Sentinel会将该实例的flags属性中打开SRI_S_DOWN标识,表示这个实例已经进入了主观下线状态

检测客观下线状态

当Sentinel将一个主服务器判断为主观下线(S_DOWN)之后,为了确认这个主服务器是否真的下线了,它会向同样监视这个主服务器的其他Sentinel进行咨询,是否其他Sentinel也认为该主服务器已经进入了下线状态,如果足够数量的Sentinel已经认为服务器下线了,那么Sentinel就会将服务器判定会客观下线,并对主服务器执行故障转移操作

  • Sentinel通过发送SENTINEL is-master-down-by-addr <ip> <port> <cuurent_epoch> <runid>命令来询问其他Sentinel,是否该主服务器已经下线(包括主观下线和客观下线)
  • 当其他Sentinel接收到询问命令后,会通过命令中的ip和端口号检查主服务器是否已经下线,然后向源Sentinel返回一条包含三个参数的Multi Bulk回复作为询问命令的回复,其中包含:
    • <down_state>:下线状态,0表示未下线,1表示下线
    • <leader_runid>:可以是*号或者目标Sentinel的局部领头Sentinel的runid,*号表示仅仅用于检测主服务器的下线状态,runid则表示用于选举领头Sentinel
    • <leader_epoch>:目标Sentinel的局部领头Sentinel的配置纪元,用于选举领头Sentinel,仅在leader_runid不为*时有效
  • 根据其他Sentinel发回的命令回复,当达到一定数量的Sentinel认为主服务器已经下线了,那么该Sentinel会将主服务器实例结构的flags数量的SRI_O_DOWN标识打开,标识主服务器已经进入了客观下线状态

选举领头Sentinel

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

  • 所有在线的Sentinel(监视同一个主服务器的)都有被选举为领头Sentinel的资格
  • 每次选举后,无论成功与否,都会将所有的Sentinel的配置纪元加1
  • 在一个配置纪元中,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元中就不能更改
  • 每次发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel
  • 当一个源Sentinel向另外的目标Sentinel发送SENTINEL is-master-down-by-addr <ip> <port> <cuurent_epoch> <runid>命令,并且命令中的runid参数不是*号而是源Sentinel的runid时,表示源Sentinel要求目标Sentinel将自己设置为后者的局部领头Sentinel
  • Sentinel设置局部领头Sentinel的规矩就是先到先得:最先发出要求的即会被设置为局部领头Sentinel
  • 目标Sentinel在接收到SENTINEL is-master-down-by-addr <ip> <port> <cuurent_epoch> <runid>命令后,会返回一条带有目标Sentinel的局部领头Sentinel的运行ID和配置纪元的命令回复
  • 源Sentinel在接收到目标Sentinel返回的命令回复后,会检查回复中的leader_epoch参数是否和自己的epoch相同,如果相同,那么源Sentinel继续取出回复中的leader_runid,如果与自己的runid一致,那么表示目标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将对已经下线的主服务器执行故障转移操作,步骤如下:

  • 在已下线的主服务器属下的所有从服务器里面,挑选出一个从服务器,作为新的主服务器
  • 让已下线的主服务器属下的所有从服务器改为复制新的主服务器
  • 将已下线的主服务器设置为新的主服务器的从服务器

挑选新的主服务器并执行SALVEOF no one命令

如何挑选新的主服务器?

领头Sentinel会将已下线的主服务器的所有从服务器保存到一个列表中,然后按照以下规则进行过滤:

  • 删除列表中所有处于下线或者断线状态的从服务器,保证列表中剩余的服务器都是在线的
  • 删除列表中最近五秒内都没有回复过领头Sentinel的INFO命令的从服务器,保证列表中剩余的从服务器都是最近成功进行通信的
  • 删除所有与已下线主服务器连接断开超过down-after-milliseconds * 10毫秒的从服务器,保证列表中剩余的从服务器的数据都是比较新的
  • 领头Sentinel根据从服务器的优先级,对列表中剩余的从服务器进行排序,并挑选出优先级最高的从服务器,如果有多个优先级最高的相等的从服务器,那么领头Sentinel将按照他们的复制偏移量,对所有具有相同高优先级的从服务器进行排序,并挑选出复制偏移量最大的,即保存的最新的数据的从服务器
  • 最后,如果有多个优先级最高,复制偏移量最大的从服务器,领头Sentinel架构按照运行ID对这些从服务器进行排序,并挑选出运行ID最小的从服务器作为新的主服务器

修改从服务器的复制目标

当新的主服务器被选举出来之后,领头Sentinel会让已下线主服务器的所有从服务器都去复制新的主服务器,通过SLAVEOF命令实现

将已下线主服务器变为从服务器

当已下线主服务器从新上线后,领头Sentinel会向它发送SLAVEOF命令,让它成为新的主服务器的从服务器

至此,故障转移完成