读书笔记|Redis Sentinel

noah2021

Redis|2022-12-11|最后更新: 2024-11-6|
type
status
date
slug
summary
tags
category
icon
password

概念

Sentinel 是原生的高可用解决方案,部署架构分为两部分:
  • Sentinel 集群:由若干个 Sentinel 节点 ( 2n+1 的奇数个 ) 组成的分布式集群,可以实现检测下线状态,故障转移
  • 数据集群:由多个 master 和这些 master 下的所有 slave 组成
notion image
Sentinel 集群负责持续监控数据集群的健康,当 master 挂掉时,⾃动选择⼀个最优的 slave 切换为 master 点。客户端来连接集群时,会⾸先连接 Sentinel,通过 Sentinel 来查询 master 的地址,然后再去连接 master 进⾏数据交互。当 master 发⽣故障时,客户端会重新向 Sentinel 要地址,Sentinel 会将最新的 master 地址告诉客户端。如此应⽤程序将⽆需重启即可⾃动完成节点切换。它的优点是:
  • 对节点的故障判断是由多个节点完成的,可以有效的防止误判
  • 高可用:相较于 Replication,Sentinel 解决了对 master 的高可用切换问题
  • 可以 ( 非必须 ) 实现一组 Sentinel 集群监控多组数据集群,但不保证数据集群之间的一致性
缺点如下:
  • 同一个数据集群下的 master 都储存相同的数据,造成内存空间浪费
  • slave 仅作为备份使用,不提供读服务
适用场景:存储的数据量不是很多且对高并发有一定需求,支持自动容错容灾的系统

使用

用户可以通过执行命令的方式启动一个 Sentinel 模式下的 Redis 服务器。下面两条启动的命令效果相同:

实现原理

启动流程

Sentinel 启动后,会有五个步骤:
  1. 初始化服务器
  1. 将普通 Redis 服务器使用的代码替换成 Sentinel 专用代码
  1. 初始化 Sentinel 状态
  1. 根据给定的配置文件, 初始化 Sentinel 的监视主服务器列表
  1. 创建连向主服务器的网络连接

初始化服务器

Sentinel 的本质是一个运行在特殊模式下的 Redis 服务器,它的初始化过程和初始化普通 Redis 服务器大致相同,不过由于其工作内容和普通服务器不同会有部分不同,比如普通服务器需要使用 RDB 或 AOF 文件来还原服务器状态,它就不用。下面列举了两者在功能上的一些差别:
notion image

使用 Sentinel 专用代码

普通服务器使用的代码在 Redis.h 里 ( 这个我没有找到,只找到了 server.h 和 server.c ),由于 Sentinel 节点的工作内容的特别,它也需要使用自己专用的代码,这个在 redis/src/sentinel.c 里。上面说到 Sentinel 节点有部分的命令不使用,就是因为没载入命令对应的代码。

初始化 Sentinel 状态

服务器会初始化一个 sentinel.c/sentinelState 结构 ( 简称 "Sentinel状态" ),这个结构保存了所有被监视的 master 的信息以及其键值对应保存的内容,服务器的一般状态仍然有 redis.h/redisServer 结构保存

初始化 Sentinel 状态的 masters 属性

具体的 master 属性上面已经写了,即 master 是一个字典,键是主节点实例的名字,值是主节点一个指向 sentinelRedisInstance 结构的指针。每个 sentinelRedisInstance 结构代表一个被 Sentinel 监视的 Redis 服务器实例, 这个实例可以是主服务器、从服务器、或者另外一个 Sentinel 。对 Sentinel 状态的初始化将引发对 masters 字典的初始化, 而 masters 字典的初始化是根据被载入的 Sentinel 配置文件来进行的。下面列出 sentinelRedisInstance 的部分属性

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

这一步是创建连向被监视主服务器的网络连接,Sentinel 将成为 master/slave 的客户端,可以向主从服务器发送命令,并从命令回复中获取相关的信息。每个被 Sentinel 监视的主从服务器,Sentinel 会创建两个连向主从服务器的异步网络连接:
  • 命令连接:用于向主服务器发送命令,并接收命令回复。用于保证 Sentinel 和 master/slave 之间的实时交互
  • 订阅连接,用于订阅主服务器的 sentinel:hello 频道,仅用于接收信息。因为在目前的发布订阅功能中,服务器不保存发送命令,用于保证不会在 Sentinel 发给 master 的信息丢失
因为 Sentinel 需要与多个实例创建多个网络连接, 所以 Sentinel 使用的是异步连接。Sentinel 会与主从结点建立命令连接和订阅连接,Sentinel 之间只会建立命令连接

功能

获取主服务器信息

Sentinel 默认每十秒一次,通过命令连接向被监视的主服务器发送 INFO 命令,并通过分析 INFO 命令回复来获取主服务器当前信息。包括两方面信息:
  • 关于服务器本身的信息:包括 run_id 域记录的服务器运行 ID,以及 role 域记录的服务器角色,用于 Sentinel 更新实例中的对应字段
  • 关于主服务器属下的所有从服务器信息:每个从服务器都由一个 “slave” 字符串开头的行记录,每行的 ip = 域记录了从服务器的 IP 地址, port = 域记录了从服务器的端口号。根据这些 IP 地址和端口号,Sentinel 无须用户提供从服务器的地址信息,就可以自动发现从服务器,用于 Sentinel 更新实例中的 slave 字典

获取从服务器信息

当 Sentinel 发现主服务器有新的服务器出现时,除了会为这个新从服务器创建相应的实例结构 ( 上面所说的 sentinelRedisInstance ) 之外,还会创建连接到从服务器的命令连接和订阅连接。创建了命令连接之后,每十秒一次向从服务器发送 INFO 命令,并根据回复分析以下信息:
从服务器的运行ID run_id 从服务器的角色 role 主服务器的ip地址 master_host 以及主服务器的端口号 master_port 主从服务器的连接状态 master_link_status 从服务器的优先级 slave_priority 从服务器的复制偏移量 slave_repl_offset
根据这些信息,Sentinel 会对从服务器的实例结构 ( sentinelRedisInstance ) 进行更新

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

Sentinel 默认每两秒一次,通过命令连接向所有被监视的主从服务器发送以下格式的命令:
其中 s 开头的表示 Sentinel 的相关参数,m 开头的按情况有所不同,如果被监视的 master 则是 master 的相关参数,如果被监视的是 slave 则是被复制的 master 的相关参数

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

在与被监视的主从服务器建立起订阅连接之后,Sentinel 会通过这个连接向服务器发送以下命令:
Sentinel 对 sentinel:hello 这个频道的订阅会一直持续到 Sentinel 与服务器的连接断开之后。对于监视同一个服务器的多个 Sentinel 来说,一个 Sentinel 发送到频道的信息其余的 Sentinel 也可以接收到,包括它自己。当收到这个消息与 runid 进行对比,如果相同则丢弃,不同则会用来更新对发送这个消息的 Sentinel 和目标服务器的认知 (更新实例结构下的 sentinels 字典、与发送信息的 sentinel 建立命令连接)。注意: sentinel 之间不会建立订阅连接。

检测主观下线

Sentinel 默认每一秒一次,通过向所有建立命令连接的实例 ( master、slave、sentinel ) 发送 PING 命令来判断对方是否在线。判断的依据是连续发送无效命令的时间是否超过 Sentinel 配置文件中 down-after-millionseconds 的时间。其中 +PONG、-LOADING、-MASTERDOWN 属于有效命令,其余都属于无效命令。如果判断某实例主观下线会将主服务器实例结构中 flags 属性的 SRI_S_DOWN 标识打开。这里 down-after-millionseconds 不仅可以判断 Sentinel 所监控的所有实例,还可以判断 master 属下的所有 slave。

检测客观下线

为确定这个服务器是否真的下线,它会向同样监视这个主服务器的其它 Sentinel 进行询问,当接收到足够数量的已下线判断之后, Sentinel 就会将从服务器判定为客观下线。
  1. 发送 SENTINEL is-master-down-by-addr 命令
    1. Sentinel会发送下面的命令询问其它Sentinel是否同意主服务器下线:
      参数
      意义
      ip
      被 Sentinel 判断为主观下线的主服务器 ip
      port
      被 Sentinel 判断为主观下线的主服务器端口号
      current_epoch
      Sentinel 当前的配置纪元,用于选举领头 Sentinel,无论是否成功都会自增一次
      runid
      可以是 * 符号或者 Sentinel 的运行 ID,* 表示命令仅仅用于检测主服务器的客观下线状态,而 Sentinel 的运行 ID 则用于选举领头 Sentinel
  1. 接收 SENTINEL is-master-down-by-addr 命令
    1. 其他 Sentinel 接收命令后,会向源Sentinel返回一个包含三个参数的 Multi Bulk 回复作为这个命令的回复
      参数
      意义
      < down_state >
      返回目标 Sentinel 对主服务器的检查结果,1表示主服务器已下线,0表示主服务器未下线
      < leader_runid >
      可以是 * 符号或者目标 Sentinel 的局部领头 Sentinel 的运行 ID,*表示命令仅仅用于检测主服务器的下线状态,而局部领头 Sentinel 的运行 ID 则用于选举领头 Sentinel
      < leader_epoch >
      目标Sentinel的局部领头Sentinel的配置纪元,用于选举领头Sentinel。仅在 leader_runid 值不为 * 时有效,如果其值为 *,这个参数总为0
  1. 统计 SENTINEL is-master-down-by-addr 命令的回复
    1. 统计同意主服务器下线的数量,当这个值达到配置指定的判断客观下线所需的数量时 ( 即 quorum 属性的值 ),Sentinel 会将主服务器实例结构中 flags 属性的 SRI_O_DOWN 标识打开

选举领头 Sentinel

当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头 Sentinel,并由领头 Sentinel 对下线主服务器进行故障转移操作。
选举领头 Sentinel 的规则和方法:
  1. 每个做主观下线的 Sentinel 节点向其他 Sentinel 节点发送 SENTINEL is-master-down-by-addr 命令,要求将它设置为局部领头
  1. 收到命令的 Sentinel 节点如果还没有同意过其他的 Sentinel 发送的命令 ( 还未投过票 ) ,那么就会发送同意命令,否则拒绝
  1. 收到目标 Sentinel 的回复后会判断 leader_runid 和 leader_epoch 是否和自己的相同,如果前者相同会继续判断后者,后者相同则表示源 Sentinel 成功被目标 Sentinel 设置成局部领头
  1. 如果该 Sentinel 节点发现自己的票数已经过半且达到了 quorum 的值,就会成为领头 Sentinel
  1. 如果这个过程出现多个 Sentinel 成为领导者,则会等待一段时间重新选举

故障转移

在选举产生出领头 Sentinel 之后,领头 Sentinel 将对已下线的 master 进行故障转移操作:
  1. 在已下线的 master 属下的所有 slave 中,挑选一个 slave 作为 master,对选中的 slave 执行 slaveof no one。之后 Sentinel 会以每一秒一次的频率向新的 master 发送 INFO 命令,若发现其 role 属性改为 master 则表明主从切换成功
  1. 让已下线的 master 的所有 slave 改为复制新的 master
  1. 将已下线 master 设置为新的主服务器的从服务器,监视这个这个旧的 master,当其重新上线时就会成为新的主服务器的从服务器

参考文章

  • Redis 设计与实现
Loading...