type
status
date
slug
summary
tags
category
icon
password
概念
Sentinel 是原生的高可用解决方案,部署架构分为两部分:
- Sentinel 集群:由若干个 Sentinel 节点 ( 2n+1 的奇数个 ) 组成的分布式集群,可以实现检测下线状态,故障转移
- 数据集群:由多个 master 和这些 master 下的所有 slave 组成
Sentinel 集群负责持续监控数据集群的健康,当 master 挂掉时,⾃动选择⼀个最优的 slave 切换为 master 点。客户端来连接集群时,会⾸先连接 Sentinel,通过 Sentinel 来查询 master 的地址,然后再去连接 master 进⾏数据交互。当 master 发⽣故障时,客户端会重新向 Sentinel 要地址,Sentinel 会将最新的 master 地址告诉客户端。如此应⽤程序将⽆需重启即可⾃动完成节点切换。它的优点是:
- 对节点的故障判断是由多个节点完成的,可以有效的防止误判
- 高可用:相较于 Replication,Sentinel 解决了对 master 的高可用切换问题
- 可以 ( 非必须 ) 实现一组 Sentinel 集群监控多组数据集群,但不保证数据集群之间的一致性
缺点如下:
- 同一个数据集群下的 master 都储存相同的数据,造成内存空间浪费
- slave 仅作为备份使用,不提供读服务
适用场景:存储的数据量不是很多且对高并发有一定需求,支持自动容错容灾的系统
使用
用户可以通过执行命令的方式启动一个 Sentinel 模式下的 Redis 服务器。下面两条启动的命令效果相同:
详情请看转载自博客园 ( 七脉 )
实现原理
启动流程
Sentinel 启动后,会有五个步骤:
- 初始化服务器
- 将普通 Redis 服务器使用的代码替换成 Sentinel 专用代码
- 初始化 Sentinel 状态
- 根据给定的配置文件, 初始化 Sentinel 的监视主服务器列表
- 创建连向主服务器的网络连接
初始化服务器
Sentinel 的本质是一个运行在特殊模式下的 Redis 服务器,它的初始化过程和初始化普通 Redis 服务器大致相同,不过由于其工作内容和普通服务器不同会有部分不同,比如普通服务器需要使用 RDB 或 AOF 文件来还原服务器状态,它就不用。下面列举了两者在功能上的一些差别:
使用 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 就会将从服务器判定为客观下线。
- 发送 SENTINEL is-master-down-by-addr 命令
Sentinel会发送下面的命令询问其它Sentinel是否同意主服务器下线:
参数 | 意义 |
ip | 被 Sentinel 判断为主观下线的主服务器 ip |
port | 被 Sentinel 判断为主观下线的主服务器端口号 |
current_epoch | Sentinel 当前的配置纪元,用于选举领头 Sentinel,无论是否成功都会自增一次 |
runid | 可以是 * 符号或者 Sentinel 的运行 ID,* 表示命令仅仅用于检测主服务器的客观下线状态,而 Sentinel 的运行 ID 则用于选举领头 Sentinel |
- 接收 SENTINEL is-master-down-by-addr 命令
其他 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 |
- 统计 SENTINEL is-master-down-by-addr 命令的回复
统计同意主服务器下线的数量,当这个值达到配置指定的判断客观下线所需的数量时 ( 即 quorum 属性的值 ),Sentinel 会将主服务器实例结构中 flags 属性的 SRI_O_DOWN 标识打开
选举领头 Sentinel
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头 Sentinel,并由领头 Sentinel 对下线主服务器进行故障转移操作。
选举领头 Sentinel 的规则和方法:
- 每个做主观下线的 Sentinel 节点向其他 Sentinel 节点发送 SENTINEL is-master-down-by-addr 命令,要求将它设置为局部领头
- 收到命令的 Sentinel 节点如果还没有同意过其他的 Sentinel 发送的命令 ( 还未投过票 ) ,那么就会发送同意命令,否则拒绝
- 收到目标 Sentinel 的回复后会判断 leader_runid 和 leader_epoch 是否和自己的相同,如果前者相同会继续判断后者,后者相同则表示源 Sentinel 成功被目标 Sentinel 设置成局部领头
- 如果该 Sentinel 节点发现自己的票数已经过半且达到了 quorum 的值,就会成为领头 Sentinel
- 如果这个过程出现多个 Sentinel 成为领导者,则会等待一段时间重新选举
故障转移
在选举产生出领头 Sentinel 之后,领头 Sentinel 将对已下线的 master 进行故障转移操作:
- 在已下线的 master 属下的所有 slave 中,挑选一个 slave 作为 master,对选中的 slave 执行 slaveof no one。之后 Sentinel 会以每一秒一次的频率向新的 master 发送 INFO 命令,若发现其 role 属性改为 master 则表明主从切换成功
- 让已下线的 master 的所有 slave 改为复制新的 master
- 将已下线 master 设置为新的主服务器的从服务器,监视这个这个旧的 master,当其重新上线时就会成为新的主服务器的从服务器
参考文章
- Redis 设计与实现