Redis 高可用(二) 哨兵理论
# 简介
Redis 的 Sentinel 系统用于管理多个 Redis 服务器, 该系统执行以下三个任务:
- 监控(Monitoring): Sentinel 会不断地检查你的 master 和 slave 是否运作正常。
- 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover): 当一个 master 不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效 master 的其中一个 slave 升级为新的 master, 并让失效 master 的其他 slave 改为复制新的 master; 当客户端试图连接失效的 master 时, 集群也会向客户端返回新 master 的地址, 使得集群可以使用新 master 代替失效服务器。
在默认情况下, Sentinel 使用 TCP 端口 26379 (普通 Redis 服务器使用的是 6379 )。
Sentinel 接受 Redis 协议格式的命令请求, 所以你可以使用 redis-cli 或者任何其他 Redis 客户端来与 Sentinel 进行通讯。
有两种方式可以和 Sentinel 进行通讯:
- 第一种方法是通过直接发送命令来查询被监视 Redis 服务器的当前状态, 以及 Sentinel 所知道的关于其他 Sentinel 的信息, 诸如此类。
- 另一种方法是使用发布与订阅功能, 通过接收 Sentinel 发送的通知: 当执行故障转移操作, 或者某个被监视的服务器被判断为主观下线或者客观下线时, Sentinel 就会发送相应的信息。
# 每个 Sentinel 都需要定期执行的任务
每个 Sentinel 以每秒钟一次的频率向它所知的 master 、slave 以及其他 Sentinel 实例发送一个 PING 命令。
如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 那么这个实例会被 Sentinel 标记为主观下线。 一个有效回复可以是: +PONG 、 -LOADING 或者 -MASTERDOWN 。
如果一个 master 被标记为主观下线, 那么正在监视这个 master 的所有 Sentinel 要以每秒一次的频率确认 master 的确进入了主观下线状态。
如果一个 master 被标记为主观下线, 并且有足够数量的 Sentinel (至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断, 那么这个 master 被标记为客观下线。
在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 master 和 slave 发送 INFO 命令。 当一个 master 被 Sentinel 标记为客观下线时, Sentinel 向下线 master 的所有 slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
当没有足够数量的 Sentinel 同意 master 已经下线, master 的客观下线状态就会被移除。 当 master 重新向 Sentinel 的 PING 命令返回有效回复时, master 的主观下线状态就会被移除。
# 自动发现 Sentinel 和从服务器
一个 Sentinel 可以与其他多个 Sentinel 进行连接, 各个 Sentinel 之间可以互相检查对方的可用性, 并进行信息交换。
你无须为运行的每个 Sentinel 分别设置其他 Sentinel 的地址, 因为 Sentinel 可以通过发布与订阅功能来自动发现正在监视相同 master 的其他 Sentinel , 这一功能是通过向频道 sentinel:hello 发送信息来实现的。
与此类似, 你也不必手动列出 master 属下的所有 slave , 因为 Sentinel 可以通过询问 master 来获得所有 slave 的信息。
每个 Sentinel 会以每两秒一次的频率, 通过发布与订阅功能, 向被它监视的所有 master 和 slave 的 sentinel:hello 频道发送一条信息, 信息中包含了 Sentinel 的 IP 地址、端口号和运行 ID (runid)。
每个 Sentinel 都订阅了被它监视的所有 master 和 slave 的 sentinel:hello 频道, 查找之前未出现过的 sentinel (looking for unknown sentinels)。 当一个 Sentinel 发现一个新的 Sentinel 时, 它会将新的 Sentinel 添加到一个列表中, 这个列表保存了 Sentinel 已知的, 监视同一个 master 的所有其他 Sentinel 。
Sentinel 发送的信息中还包括完整的 master 当前配置(configuration)。 如果一个 Sentinel 包含的 master 配置比另一个 Sentinel 发送的配置要旧, 那么这个 Sentinel 会立即升级到新配置上。
在将一个新 Sentinel 添加到监视 master 的列表上面之前, Sentinel 会先检查列表中是否已经包含了和要添加的 Sentinel 拥有相同运行 ID 或者相同地址(包括 IP 地址和端口号)的 Sentinel , 如果是的话, Sentinel 会先移除列表中已有的那些拥有相同运行 ID 或者相同地址的 Sentinel , 然后再添加新 Sentinel 。
# 故障转移
一次故障转移操作由以下步骤组成:
- 发现 master 已经进入客观下线状态。
- 对我们的当前 epoch 值进行自增, 并尝试在这个 epoch 值中当选。
- 如果当选失败, 那么在设定的故障迁移超时时间的两倍之后, 重新尝试当选。 如果当选成功, 那么执行以下步骤。
- 选出一个 slave,并将它升级为 master。
- 向被选中的 slave 发送
SLAVEOF NO ONE
命令,让它转变为 master。 - 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。
- 向已下线 master 的 slave 发送 SLAVEOF 命令, 让它们去复制新的 master。
- 当所有 slave 都已经开始复制新的 master 时, 领头 Sentinel 终止这次故障迁移操作。
每当一个 Redis 实例被重新配置(reconfigured) —— 无论是被设置成 master、slave、又或者被设置成其他 master 的 slave —— Sentinel 都会向被重新配置的实例发送一个 CONFIG REWRITE 命令, 从而确保这些配置会持久化在硬盘里。
Sentinel 使用以下规则来选择新的 master:
- 在失效 master 属下的 slave 当中, 那些被标记为主观下线、已断线、或者最后一次回复 PING 命令的时间大于五秒钟的 slave 都会被淘汰。
- 在失效 master 属下的 slave 当中, 那些与失效 master 连接断开的时长超过 down-after 选项指定的时长十倍的 slave 都会被淘汰。
- 在经历了以上两轮淘汰之后剩下来的 slave 中, 我们选出复制偏移量(replication offset)最大的那个 slave 作为新的 master; 如果复制偏移量不可用, 或者 slave 的复制偏移量相同, 那么带有最小运行 ID 的那个 slave 成为新的 master。
# Sentinel 自动故障迁移的一致性特质
Sentinel 自动故障迁移使用 Raft 算法来选举 leader(组长)Sentinel , 从而确保在一个给定的 epoch 值里, 只有一个 leader 产生。
这表示在同一个 epoch 值中, 不会有两个 Sentinel 同时被选中为 leader, 并且各个 Sentinel 在同一个 epoch 值中只会对一个 leader 进行投票。
更高的配置 epoch 值总是优于较低的 epoch 值, 因此每个 Sentinel 都会主动使用更新的 epoch 值来代替自己的配置。
简单来说, 我们可以将 Sentinel 配置看作是一个带有版本号的状态。 一个状态会以最后写入者胜出(last-write-wins)的方式(也即是,最新的配置总是胜出)传播至所有其他 Sentinel 。
举个例子, 当出现网络分割(network partitions)时, 一个 Sentinel 可能会包含了较旧的配置, 而当这个 Sentinel 接到其他 Sentinel 发来的版本更新的配置时, Sentinel 就会对自己的配置进行更新。
如果要在网络分割出现的情况下仍然保持一致性, 那么应该使用 min-slaves-to-write 选项, 让 master 在连接的从实例少于给定数量时停止执行写操作, 与此同时, 应该在每个运行 Redis master 或 slave 的机器上运行 Redis Sentinel 进程。
# Sentinel 状态的持久化
Sentinel 的状态会被持久化在 Sentinel 配置文件里面。
每当 Sentinel 接收到一个新的配置, 或者当组长 Sentinel 为 master 创建一个新的配置时, 这个配置会与配置 epoch 值一起被保存到磁盘里面。
这意味着停止和重启 Sentinel 进程都是安全的。
# Sentinel 在非故障迁移的情况下对实例进行重新配置
即使没有自动故障迁移操作在进行, Sentinel 总会尝试将当前的配置设置到被监视的实例上面。 特别是:根据当前的配置, 如果一个 slave 被宣告为 master, 那么它会代替原有的 master, 成为新的 master, 并且成为原有 master 的所有 slave 的复制对象。 那些连接了错误 master 的 slave 会被重新配置, 使得这些 slave 会去复制正确的 master。
不过, 在以上这些条件满足之后, Sentinel 在对实例进行重新配置之前仍然会等待一段足够长的时间, 确保可以接收到其他 Sentinel 发来的配置更新, 从而避免自身因为保存了过期的配置而对实例进行了不必要的重新配置。
# TILT 模式
Redis Sentinel 严重依赖计算机的时间功能: 比如说, 为了判断一个实例是否可用, Sentinel 会记录这个实例最后一次相应 PING 命令的时间, 并将这个时间和当前时间进行对比, 从而知道这个实例有多长时间没有和 Sentinel 进行任何成功通讯。
不过, 一旦计算机的时间功能出现故障, 或者计算机非常忙碌, 又或者进程因为某些原因而被阻塞时, Sentinel 可能也会跟着出现故障。
TILT 模式是一种特殊的保护模式: 当 Sentinel 发现系统有些不对劲时, Sentinel 就会进入 TILT 模式。
因为 Sentinel 的时间中断器默认每秒执行 10 次, 所以我们预期时间中断器的两次执行之间的间隔为 100 毫秒左右。 Sentinel 的做法是, 记录上一次时间中断器执行时的时间, 并将它和这一次时间中断器执行的时间进行对比:
如果两次调用时间之间的差距为负值, 或者非常大(超过 2 秒钟), 那么 Sentinel 进入 TILT 模式。
如果 Sentinel 已经进入 TILT 模式, 那么 Sentinel 延迟退出 TILT 模式的时间。
当 Sentinel 进入 TILT 模式时, 它仍然会继续监视所有目标, 但是:它不再执行任何操作,比如故障转移。当有实例向这个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令时, Sentinel 返回负值: 因为这个 Sentinel 所进行的下线判断已经不再准确。如果 TILT 可以正常维持 30 秒钟, 那么 Sentinel 退出 TILT 模式。
# Sentinel 命令
以下列出的是 Sentinel 接受的命令:
命令 | 描述 |
---|---|
PING | 如果后面没有参数时返回 PONG,否则会返回后面带的参数。这个命令经常用来测试一个连接是否还是可用的,或者用来测试一个连接的延时。如果客户端处于频道订阅模式下,它将是一个 multi-bulk 返回,第一次时返回”pong”,之后返回空(empty bulk),除非命令后面更随了参数。 |
SENTINEL masters | 列出所有被监视的 master,以及这些 master 的当前状态。 |
SENTINEL slaves | 列出给定 master 的所有 slave ,以及这些 slave 的当前状态 |
SENTINEL get-master-addr-by-name | 返回给定名字的 master 的 IP 地址和端口号。 如果这个 master 正在执行故障转移操作, 或者针对这个 master 的故障转移操作已经完成, 那么这个命令返回新的 master 的 IP 地址和端口号 |
SENTINEL reset | 重置所有名字和给定模式 pattern 相匹配的 master 。 pattern 参数是一个 Glob 风格的模式。 重置操作清楚 master 目前的所有状态, 包括正在执行中的故障转移, 并移除目前已经发现和关联的, master 的所有 slave 和 Sentinel |
SENTINEL failover | 当 master 失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移 (不过发起故障转移的 Sentinel 会向其他 Sentinel 发送一个新的配置,其他 Sentinel 会根据这个配置进行相应的更新) |