Redis Sentinel 高可用性

非集群 Redis 的高可用性

Redis 开源版

Redis Sentinel 在不使用 Redis Cluster 时,为 Redis 提供高可用性。

Redis Sentinel 还提供其他附属任务,例如监控、通知,并充当客户端的配置提供者。

这是宏观层面(即全貌)的 Sentinel 功能完整列表

  • 监控。Sentinel 会持续检查你的主节点和副本节点实例是否按预期工作。
  • 通知。Sentinel 可以通过 API 通知系统管理员或其他计算机程序,表明某个被监控的 Redis 实例出了问题。
  • 自动故障转移。如果主节点未按预期工作,Sentinel 可以启动故障转移过程,将一个副本节点提升为主节点,其他额外的副本节点被重新配置以使用新的主节点,并且使用 Redis 服务器的应用会被告知连接时使用的新地址。
  • 配置提供者。Sentinel 作为客户端服务发现的权威来源:客户端连接到 Sentinel 以查询负责给定服务的当前 Redis 主节点的地址。如果发生故障转移,Sentinel 将报告新地址。

作为分布式系统的 Sentinel

Redis Sentinel 是一个分布式系统

Sentinel 本身设计为在多个 Sentinel 进程协同工作的配置中运行。多个 Sentinel 进程协同工作的优势如下

  1. 当多个 Sentinel 就给定主节点不再可用这一事实达成一致时,执行故障检测。这降低了误报的概率。
  2. 即使并非所有 Sentinel 进程都在工作,Sentinel 也能工作,这使得系统能够抵御故障。毕竟,拥有一个本身就是单点故障的故障转移系统没什么意义。

Sentinel、Redis 实例(主节点和副本节点)以及连接到 Sentinel 和 Redis 的客户端的总和,也是一个具有特定属性的更大的分布式系统。本文档将从理解 Sentinel 基本属性所需的基础信息开始,逐步介绍概念,再到更复杂的信息(可选),以便理解 Sentinel 具体是如何工作的。

Sentinel 快速入门

获取 Sentinel

当前版本的 Sentinel 称为 Sentinel 2。它是对初始 Sentinel 实现的重写,使用了更强大且更易于预测的算法(这些算法在本文档中有所解释)。

自 Redis 2.8 起发布了 Redis Sentinel 的稳定版本。

新的开发在不稳定分支中进行,新特性有时在被认为稳定后,会立即移植到最新的稳定分支中。

Redis 2.6 附带的 Redis Sentinel 版本 1 已被弃用,不应再使用。

运行 Sentinel

如果你使用 redis-sentinel 可执行文件(或者如果你有一个指向 redis-server 可执行文件的同名符号链接),你可以使用以下命令行运行 Sentinel

redis-sentinel /path/to/sentinel.conf

否则你可以直接使用 redis-server 可执行文件,以 Sentinel 模式启动它

redis-server /path/to/sentinel.conf --sentinel

两种方式效果相同。

然而,在运行 Sentinel 时必须使用配置文件,因为系统将使用此文件保存当前状态,以便在重启时重新加载。如果没有提供配置文件或配置文件路径不可写,Sentinel 将拒绝启动。

Sentinel 默认运行在监听 TCP 端口 26379 的连接,因此 Sentinel 要工作,你的服务器的端口 26379 必须开放以接收来自其他 Sentinel 实例 IP 地址的连接。否则 Sentinel 无法通信,也无法就下一步行动达成一致,因此不会执行故障转移。

部署 Sentinel 前需了解的基本事项

  1. 你需要至少三个 Sentinel 实例来实现健壮的部署。
  2. 这三个 Sentinel 实例应放置在被认为会独立发生故障的计算机或虚拟机中。例如,不同的物理服务器或运行在不同可用区的虚拟机。
  3. Sentinel + Redis 分布式系统不能保证在故障期间保留已确认的写入,因为 Redis 使用异步复制。然而,有一些部署 Sentinel 的方式可以将丢失写入的时间窗口限制在特定时刻,而另一些部署方式则安全性较低。
  4. 你的客户端需要支持 Sentinel。主流的客户端库支持 Sentinel,但并非所有都支持。
  5. 如果你不时在开发环境中测试,甚至最好能在生产环境中测试,那么任何 HA 设置都不是绝对安全的。你可能存在一个配置错误,只有在为时已晚时(例如凌晨 3 点主节点停止工作时)才会显现出来。
  6. Sentinel、Docker 或其他形式的网络地址转换 (NAT) 或端口映射应谨慎混合使用:Docker 执行端口重映射,这会破坏 Sentinel 自动发现其他 Sentinel 进程的功能以及获取主节点副本列表的功能。请查阅本文档稍后关于 Sentinel 和 Docker 部分,获取更多信息。

配置 Sentinel

Redis 源码分发包中包含一个名为 sentinel.conf 的文件,它是一个自文档化的配置示例文件,你可以用它来配置 Sentinel,然而一个典型的最小配置文件如下所示

sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1

sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5

你只需要指定要监控的主节点,为每个独立的主节点(可能拥有任意数量的副本节点)赋予不同的名称。无需指定副本节点,它们是自动发现的。Sentinel 会自动更新配置,添加有关副本节点的额外信息(以便在重启时保留信息)。每次在故障转移期间副本节点被提升为主节点,以及发现新的 Sentinel 时,配置也会被重写。

上述配置示例基本上监控两组 Redis 实例,每组由一个主节点和数量不定的副本节点组成。其中一组实例名为 mymaster,另一组名为 resque

sentinel monitor 语句的参数含义如下

sentinel monitor <master-name> <ip> <port> <quorum>

为了清晰起见,我们逐行检查这些配置选项的含义

第一行用于告诉 Redis 监控一个名为 mymaster 的主节点,其地址为 127.0.0.1,端口为 6379,仲裁数为 2。除了仲裁数参数之外,一切都很明显

  • 仲裁数是需要就主节点不可达这一事实达成一致的 Sentinel 数量,以便真正将主节点标记为失败,并最终在可能的情况下启动故障转移过程。
  • 然而,仲裁数仅用于检测故障。为了实际执行故障转移,其中一个 Sentinel 需要被选举为故障转移的领导者并获得授权才能继续。这只有在大多数 Sentinel 进程的投票下才能发生。

所以举例来说,如果你有 5 个 Sentinel 进程,并且给定主节点的仲裁数设置为 2,那么会发生以下情况

  • 如果有两个 Sentinel 同时就主节点不可达达成一致,其中一个将尝试启动故障转移。
  • 如果总共有至少三个 Sentinel 可达,故障转移将被授权并实际开始。

实际上这意味着在故障期间,如果大多数 Sentinel 进程无法通信,Sentinel 永远不会启动故障转移(即少数派分区中不会发生故障转移)。

其他 Sentinel 选项

其他选项几乎总是采用以下形式

sentinel <option_name> <master_name> <option_value>

并用于以下目的

  • down-after-milliseconds 是一个实例持续不可达(要么不回复我们的 PING,要么回复错误)的毫秒数,达到此时间后 Sentinel 会开始认为它已下线。
  • parallel-syncs 设置在故障转移后可以同时重新配置以使用新主节点的副本节点数量。这个数值越低,故障转移过程完成所需的时间越长,但如果副本节点被配置为提供旧数据,你可能不希望所有副本节点同时与主节点重新同步。虽然复制过程对于副本节点来说大部分时间是非阻塞的,但有一个时刻它会停止以加载主节点的批量数据。你可能希望通过将此选项设置为 1 来确保一次只有一个副本节点不可达。

更多选项在本文档的其余部分描述,并在 Redis 分发包附带的示例 sentinel.conf 文件中进行了说明。

配置参数可以在运行时修改

  • 特定于主节点的配置参数使用 SENTINEL SET 命令修改。
  • 全局配置参数使用 SENTINEL CONFIG SET 命令修改。

参见 在运行时重新配置 Sentinel 部分以获取更多信息。

Sentinel 部署示例

现在你了解了关于 Sentinel 的基本信息,你可能想知道应该把 Sentinel 进程放在哪里,需要多少个 Sentinel 进程等等。本节展示了一些部署示例。

我们使用 ASCII 艺术来以图形格式展示配置示例,以下是不同符号的含义

+--------------------+
| This is a computer |
| or VM that fails   |
| independently. We  |
| call it a "box"    |
+--------------------+

方框内写明其中运行的内容

+-------------------+
| Redis master M1   |
| Redis Sentinel S1 |
+-------------------+

不同的方框之间用线连接,表示它们可以相互通信

+-------------+               +-------------+
| Sentinel S1 |---------------| Sentinel S2 |
+-------------+               +-------------+

网络分区显示为使用斜杠中断的线

+-------------+                +-------------+
| Sentinel S1 |------ // ------| Sentinel S2 |
+-------------+                +-------------+

另请注意

  • 主节点称为 M1, M2, M3, ..., Mn。
  • 副本节点称为 R1, R2, R3, ..., Rn (R 代表副本)。
  • Sentinel 称为 S1, S2, S3, ..., Sn。
  • 客户端称为 C1, C2, C3, ..., Cn。
  • 当一个实例因 Sentinel 操作而改变角色时,我们将其放在方括号内,因此 [M1] 表示一个因 Sentinel 介入而现在成为主节点的实例。

注意,我们永远不会展示仅使用两个 Sentinel 的设置,因为 Sentinel 总是需要与多数派通信才能开始故障转移。

示例 1:仅使用两个 Sentinel,请勿这样做

+----+         +----+
| M1 |---------| R1 |
| S1 |         | S2 |
+----+         +----+

Configuration: quorum = 1
  • 在此设置中,如果主节点 M1 失败,R1 将被提升,因为两个 Sentinel 可以就故障达成一致(显然仲裁数设置为 1),并且因为多数派是两个,它们也可以授权故障转移。所以表面上看它似乎可以工作,但请检查以下几点以了解为什么这种设置是不可行的。
  • 如果运行 M1 的服务器停止工作,S1 也会停止工作。在另一台服务器上运行的 Sentinel S2 将无法授权故障转移,因此系统将变为不可用。

注意,需要多数派才能协调不同的故障转移,并在之后将最新配置传播给所有 Sentinel。另请注意,在上述设置的单方面进行故障转移而不达成任何一致,将非常危险

+----+           +------+
| M1 |----//-----| [M1] |
| S1 |           | S2   |
+----+           +------+

在上述配置中,我们以完全对称的方式创建了两个主节点(假设 S2 可以在未经授权的情况下进行故障转移)。客户端可以无限期地向两侧写入数据,并且无法确定在分区恢复时哪个配置是正确的,从而无法阻止永久脑裂状态的发生。

所以请始终在三台不同的服务器上部署至少三个 Sentinel

示例 2:三个服务器的基本设置

这是一个非常简单的设置,其优点是易于调整以增加安全性。它基于三台服务器,每台服务器都运行一个 Redis 进程和一个 Sentinel 进程。

       +----+
       | M1 |
       | S1 |
       +----+
          |
+----+    |    +----+
| R2 |----+----| R3 |
| S2 |         | S3 |
+----+         +----+

Configuration: quorum = 2

如果主节点 M1 失败,S2 和 S3 将就故障达成一致,并能够授权故障转移,使客户端能够继续工作。

在每个 Sentinel 设置中,由于 Redis 使用异步复制,始终存在丢失部分写入的风险,因为某个已确认的写入可能无法到达被提升为主节点的副本节点。然而,在上述设置中,由于客户端与旧主节点一同被分区隔离,风险更高,如下图所示

         +----+
         | M1 |
         | S1 | <- C1 (writes will be lost)
         +----+
            |
            /
            /
+------+    |    +----+
| [M2] |----+----| R3 |
| S2   |         | S3 |
+------+         +----+

在这种情况下,网络分区隔离了旧主节点 M1,因此副本节点 R2 被提升为主节点。然而,与旧主节点位于同一分区的客户端,例如 C1,可能会继续向旧主节点写入数据。这些数据将永远丢失,因为当分区恢复时,旧主节点将被重新配置为新主节点的副本节点,丢弃其数据集。

使用以下 Redis 复制特性可以缓解此问题,该特性允许主节点在检测到无法将其写入传输到指定数量的副本节点时停止接受写入。

min-replicas-to-write 1
min-replicas-max-lag 10

使用上述配置(请参阅 Redis 分发包中自注释的 redis.conf 示例以获取更多信息),一个 Redis 实例在充当主节点时,如果无法写入至少 1 个副本节点,将停止接受写入。由于复制是异步的,无法写入实际上意味着该副本节点要么已断开连接,要么在超过指定的 max-lag 秒数后未向我们发送异步确认。

使用此配置,上例中的旧 Redis 主节点 M1 将在 10 秒后变为不可用。当分区恢复时,Sentinel 配置将收敛到新配置,客户端 C1 将能够获取有效的配置并继续使用新的主节点。

然而,没有免费的午餐。通过此改进,如果两个副本节点都下线了,主节点将停止接受写入。这是一个权衡。

示例 3:客户端服务器中的 Sentinel

有时我们只有两台 Redis 服务器可用,一台用于主节点,一台用于副本节点。示例 2 中的配置在这种情况下不可行,因此我们可以采用以下方案,将 Sentinel 放在客户端所在的服务器上

            +----+         +----+
            | M1 |----+----| R1 |
            |    |    |    |    |
            +----+    |    +----+
                      |
         +------------+------------+
         |            |            |
         |            |            |
      +----+        +----+      +----+
      | C1 |        | C2 |      | C3 |
      | S1 |        | S2 |      | S3 |
      +----+        +----+      +----+

      Configuration: quorum = 2

在此设置中,Sentinel 的视角与客户端相同:如果主节点可被大多数客户端访问,那么就没问题。这里的 C1、C2、C3 是泛指客户端,这并不意味着 C1 代表连接到 Redis 的单个客户端。它更可能代表应用服务器、Rails 应用之类的东西。

如果运行 M1 和 S1 的服务器发生故障,故障转移将顺利进行,然而,很容易看出不同的网络分区会导致不同的行为。例如,如果客户端与 Redis 服务器之间的网络断开,Sentinel 将无法进行设置,因为 Redis 主节点和副本节点都将不可用。

注意,如果 C3 与 M1 一起被分区(在上述网络结构下不太可能,但在不同布局或软件层故障下更有可能),我们将遇到与示例 2 中描述的类似问题,区别在于这里我们无法打破对称性,因为只有一个副本节点和一个主节点,所以主节点在与副本节点断开连接时不能停止接受查询,否则在副本节点故障期间主节点将永远不可用。

因此,这是一种有效的设置,但示例 2 中的设置具有优势,例如 Redis 的 HA 系统与 Redis 本身运行在同一台服务器上,管理起来可能更简单,并且能够限制少数派分区中的主节点接收写入的时间量。

示例 4:客户端服务器少于三台的 Sentinel 设置

如果客户端服务器少于三台(例如三台 Web 服务器),则无法使用示例 3 中描述的设置。在这种情况下,我们需要采用如下所示的混合设置

            +----+         +----+
            | M1 |----+----| R1 |
            | S1 |    |    | S2 |
            +----+    |    +----+
                      |
               +------+-----+
               |            |
               |            |
            +----+        +----+
            | C1 |        | C2 |
            | S3 |        | S4 |
            +----+        +----+

      Configuration: quorum = 3

这与示例 3 中的设置类似,但在这里我们在现有的四台服务器上运行四个 Sentinel。如果主节点 M1 不可用,其他三个 Sentinel 将执行故障转移。

理论上,如果移除运行 C2 和 S4 的服务器并将仲裁数设置为 2,这种设置也能工作。然而,在应用层没有高可用性的情况下,我们不太可能希望 Redis 侧有 HA。

Sentinel、Docker、NAT 和可能的问题

Docker 使用一种称为端口映射的技术:运行在 Docker 容器内的程序可能会以与程序认为正在使用的端口不同的端口暴露。这对于在同一服务器上同时运行使用相同端口的多个容器非常有用。

Docker 不是唯一发生这种情况的软件系统,还存在其他网络地址转换 (NAT) 设置,其中端口可能会被重映射,有时不仅是端口,还包括 IP 地址。

重新映射端口和地址会以两种方式对 Sentinel 产生问题

  1. Sentinel 自动发现其他 Sentinel 的功能不再有效,因为它基于hello消息,每条消息中每个 Sentinel 都会宣布它们正在监听连接的端口和 IP 地址。然而,Sentinel 无法理解地址或端口是否已被重映射,因此它们宣布的信息对于其他 Sentinel 连接是不正确的。
  2. 副本节点列在 Redis 主节点的 INFO 输出中,方式类似:地址由主节点通过检查 TCP 连接的远端对等体检测到,而端口由副本节点自身在握手期间通告,但是,端口可能因与第 1 点相同的原因而出错。

由于 Sentinel 使用主节点的 INFO 输出信息自动检测副本节点,检测到的副本节点将无法访问,并且 Sentinel 将无法对主节点进行故障转移,因为从系统的角度来看没有可用的良好副本节点,因此目前无法使用 Sentinel 监控通过 Docker 部署的主节点和副本节点实例集合,除非你指示 Docker 进行 1:1 端口映射

对于第一个问题,如果你想使用 Docker 并转发端口(或任何其他端口被重映射的 NAT 设置)运行一组 Sentinel 实例,你可以使用以下两个 Sentinel 配置指令来强制 Sentinel 公布特定的 IP 和端口

sentinel announce-ip <ip>
sentinel announce-port <port>

注意 Docker 具有在主机网络模式中运行的能力(请查阅 --net=host 选项了解更多信息)。在这种设置下端口未被重映射,因此不应产生问题。

IP 地址和 DNS 名称

早期版本的 Sentinel 不支持主机名,并且要求在所有地方指定 IP 地址。从 6.2 版本开始,Sentinel 支持可选的主机名支持。

此功能默认禁用。如果你打算启用 DNS/主机名支持,请注意

  1. 你的 Redis 和 Sentinel 节点上的名称解析配置必须可靠,并能快速解析地址。地址解析中的意外延迟可能会对 Sentinel 产生负面影响。
  2. 你应该在所有地方使用主机名,并避免混用主机名和 IP 地址。为此,请对所有 Redis 和 Sentinel 实例分别使用 replica-announce-ip <hostname>sentinel announce-ip <hostname>

启用 resolve-hostnames 全局配置允许 Sentinel 接受主机名

  • 作为 sentinel monitor 命令的一部分
  • 作为副本地址,如果副本节点对 replica-announce-ip 使用主机名值

Sentinel 将接受主机名作为有效输入并解析它们,但在公布实例、更新配置文件等时仍将引用 IP 地址。

启用 announce-hostnames 全局配置使 Sentinel 转而使用主机名。这会影响对客户端的回复、配置文件中写入的值、对副本节点发出的 REPLICAOF 命令等。

此行为可能与并非所有 Sentinel 客户端兼容,这些客户端可能明确期望一个 IP 地址。

当客户端使用 TLS 连接实例并需要名称而不是 IP 地址来执行证书 ASN 匹配时,使用主机名可能会很有用。

快速教程

在本文档的后续章节中,将逐步介绍关于 Sentinel API、配置和语义的所有详细信息。然而对于希望尽快试用该系统的人来说,本节是一个教程,展示了如何配置和与 3 个 Sentinel 实例交互。

这里我们假设实例运行在端口 5000、5001、5002。我们还假设你有一个运行在端口 6379 的 Redis 主节点和一个运行在端口 6380 的副本节点。在整个教程中,我们将使用 IPv4 回环地址 127.0.0.1,假设你在你的个人电脑上运行模拟。

三个 Sentinel 配置文件应如下所示

port 5000
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

其他两个配置文件将完全相同,但分别使用 5001 和 5002 作为端口号。

关于上述配置需要注意的几点

  • 主节点集合名为 mymaster。它标识了主节点及其副本节点。由于每个主节点集合都有不同的名称,Sentinel 可以同时监控不同的主节点和副本节点集合。
  • 仲裁数设置为 2(sentinel monitor 配置指令的最后一个参数)。
  • down-after-milliseconds 值是 5000 毫秒,即 5 秒,因此只要在此时间内我们没有收到来自主节点的任何 PING 回复,主节点就会被检测为失败。

启动这三个 Sentinel 后,你会看到它们记录的一些消息,例如

+monitor master mymaster 127.0.0.1 6379 quorum 2

这是一个 Sentinel 事件,如果你 SUBSCRIBE 到事件名称,就可以通过 Pub/Sub 接收此类事件,如稍后在 Pub/Sub 消息 部分中指定。

Sentinel 在故障检测和故障转移期间生成并记录不同的事件。

查询 Sentinel 关于主节点的状态

开始使用 Sentinel 最明显的事情就是检查它正在监控的主节点是否运行良好

$ redis-cli -p 5000
127.0.0.1:5000> sentinel master mymaster
 1) "name"
 2) "mymaster"
 3) "ip"
 4) "127.0.0.1"
 5) "port"
 6) "6379"
 7) "runid"
 8) "953ae6a589449c13ddefaee3538d356d287f509b"
 9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "735"
19) "last-ping-reply"
20) "735"
21) "down-after-milliseconds"
22) "5000"
23) "info-refresh"
24) "126"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "532439"
29) "config-epoch"
30) "1"
31) "num-slaves"
32) "1"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "60000"
39) "parallel-syncs"
40) "1"

如你所见,它打印了关于主节点的许多信息。其中有几项对我们特别重要

  1. num-other-sentinels 是 2,所以我们知道 Sentinel 已经检测到这个主节点的另外两个 Sentinel。如果你查看日志,你会看到生成的 +sentinel 事件。
  2. flags 只是 master。如果主节点下线了,我们可能会看到 s_downo_down 标志。
  3. num-slaves 被正确设置为 1,所以 Sentinel 也检测到我们的主节点连接了一个副本。

为了进一步了解这个实例,你可以尝试以下两个命令

SENTINEL replicas mymaster
SENTINEL sentinels mymaster

第一个命令将提供关于连接到主节点的副本的类似信息,第二个命令将提供关于其他 Sentinel 的信息。

获取当前主节点的地址

正如我们已经指出的,Sentinel 也为希望连接到一组主节点和副本的客户端提供配置信息。由于可能发生的故障转移或重新配置,客户端不知道给定实例集中谁是当前活动的主节点,因此 Sentinel 导出一个 API 来询问这个问题

127.0.0.1:5000> SENTINEL get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6379"

测试故障转移

至此,我们的 Sentinel 模拟部署已准备好进行测试。我们可以直接杀死我们的主节点,然后检查配置是否改变。为此,我们可以这样做

redis-cli -p 6379 DEBUG sleep 30

这个命令将使我们的主节点无法访问,并休眠 30 秒。它基本模拟了主节点由于某种原因挂起的情况。

如果你查看 Sentinel 日志,你应该能看到很多活动

  1. 每个 Sentinel 通过一个 +sdown 事件检测到主节点已宕机。
  2. 这个事件随后升级为 +odown,这意味着多个 Sentinel 一致认为主节点不可访问。
  3. Sentinel 们投票选出一个 Sentinel 来启动第一次故障转移尝试。
  4. 故障转移发生了。

如果你再次询问 mymaster 的当前主节点地址是什么,这次我们最终应该会得到一个不同的回复

127.0.0.1:5000> SENTINEL get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6380"

到目前为止一切顺利... 至此,你可以开始创建你的 Sentinel 部署,或者可以继续阅读以了解所有 Sentinel 命令和内部机制。

Sentinel API

Sentinel 提供了一个 API,用于检查其状态、检查被监控主节点和副本的健康状况、订阅以接收特定通知以及在运行时更改 Sentinel 配置。

默认情况下,Sentinel 使用 TCP 端口 26379 运行(注意 6379 是正常的 Redis 端口)。Sentinel 接受使用 Redis 协议的命令,因此你可以使用 redis-cli 或任何其他未经修改的 Redis 客户端与 Sentinel 通信。

可以直接查询 Sentinel,从其角度检查被监控 Redis 实例的状态,查看它知道的其他 Sentinel 等等。此外,使用 Pub/Sub,可以在每次发生某些事件时(例如故障转移或实例进入错误状态等)从 Sentinel 接收推送式通知。

Sentinel 命令

SENTINEL 命令是 Sentinel 的主要 API。以下是其子命令列表(适用时标注最低版本)

  • SENTINEL CONFIG GET <name> (>= 6.2) 获取全局 Sentinel 配置参数的当前值。指定的名称可以是通配符,类似于 Redis 的 CONFIG GET 命令。
  • SENTINEL CONFIG SET <name> <value> (>= 6.2) 设置全局 Sentinel 配置参数的值。
  • SENTINEL CKQUORUM <master name> 检查当前的 Sentinel 配置是否能够达到故障转移主节点所需的仲裁数 (quorum),以及授权故障转移所需的大多数 (majority)。此命令应在监控系统中使用,以检查 Sentinel 部署是否正常。
  • SENTINEL FLUSHCONFIG 强制 Sentinel 将其配置(包括当前 Sentinel 状态)重写到磁盘。通常,Sentinel 会在其状态发生变化时(指跨重启后持久化到磁盘的状态子集)重写配置。然而,有时由于操作错误、磁盘故障、软件包升级脚本或配置管理器,配置文件可能会丢失。在这种情况下,强制 Sentinel 重写配置文件的功能非常方便。此命令即使在之前的配置文件完全丢失的情况下也能工作。
  • SENTINEL FAILOVER <master name> 强制执行故障转移,就像主节点不可访问一样,且不询问其他 Sentinel 的同意(但是,将发布新版本的配置,以便其他 Sentinel 更新其配置)。
  • SENTINEL GET-MASTER-ADDR-BY-NAME <master name> 返回具有该名称的主节点的 IP 地址和端口号。如果此主节点正在进行故障转移或已成功完成,则返回被提升为新主节点的副本的地址和端口。
  • SENTINEL INFO-CACHE (>= 3.2) 返回主节点和副本的缓存 INFO 输出。
  • SENTINEL IS-MASTER-DOWN-BY-ADDR 从当前 Sentinel 的角度检查指定 IP:Port 的主节点是否宕机。此命令主要用于内部使用。
  • SENTINEL MASTER <master name> 显示指定主节点的状态和信息。
  • SENTINEL MASTERS 显示被监控主节点的列表及其状态。
  • SENTINEL MONITOR 启动 Sentinel 的监控。有关详细信息,请参阅 在运行时重新配置 Sentinel 部分
  • SENTINEL MYID (>= 6.2) 返回 Sentinel 实例的 ID。
  • SENTINEL PENDING-SCRIPTS 此命令返回有关待处理脚本的信息。
  • SENTINEL REMOVE 停止 Sentinel 的监控。有关详细信息,请参阅 在运行时重新配置 Sentinel 部分
  • SENTINEL REPLICAS <master name> (>= 5.0) 显示此主节点的副本列表及其状态。
  • SENTINEL SENTINELS <master name> 显示此主节点的 Sentinel 实例列表及其状态。
  • SENTINEL SET 设置 Sentinel 的监控配置。有关详细信息,请参阅 在运行时重新配置 Sentinel 部分
  • SENTINEL SIMULATE-FAILURE (crash-after-election|crash-after-promotion|help) (>= 3.2) 此命令模拟不同的 Sentinel 崩溃场景。
  • SENTINEL RESET <pattern> 此命令将重置所有匹配名称的主节点。pattern 参数是一个 glob 风格的模式。重置过程会清除主节点中的任何先前状态(包括正在进行的故障转移),并移除已发现并与该主节点关联的所有副本和 Sentinel。

为了连接管理和管理目的,Sentinel 支持以下 Redis 命令子集

  • ACL (>= 6.2) 此命令管理 Sentinel 访问控制列表。有关详细信息,请参阅 ACL 文档页面和 Sentinel 访问控制列表认证
  • AUTH (>= 5.0.1) 验证客户端连接。有关详细信息,请参阅 AUTH 命令和 使用身份验证配置 Sentinel 实例 部分
  • CLIENT 此命令管理客户端连接。有关详细信息,请参阅其子命令页面。
  • COMMAND (>= 6.2) 此命令返回有关命令的信息。有关详细信息,请参阅 COMMAND 命令及其各种子命令。
  • HELLO (>= 6.0) 切换连接的协议。有关详细信息,请参阅 HELLO 命令。
  • INFO 返回有关 Sentinel 服务器的信息和统计数据。有关详细信息,请参见 INFO 命令。
  • PING 此命令仅返回 PONG。
  • ROLE 此命令返回字符串 "sentinel" 和被监控主节点的列表。有关详细信息,请参阅 ROLE 命令。
  • SHUTDOWN 关闭 Sentinel 实例。

最后,Sentinel 还支持 SUBSCRIBEUNSUBSCRIBEPSUBSCRIBEPUNSUBSCRIBE 命令。有关详细信息,请参阅 Pub/Sub 消息 部分

在运行时重新配置 Sentinel

从 Redis 2.8.4 版本开始,Sentinel 提供了一个 API,用于添加、移除或更改给定主节点的配置。请注意,如果您有多个 Sentinel,您应该将更改应用到所有实例上,以便 Redis Sentinel 正常工作。这意味着更改单个 Sentinel 的配置不会自动将更改传播到网络中的其他 Sentinel。

以下是用于更新 Sentinel 实例配置的 SENTINEL 子命令列表。

  • SENTINEL MONITOR <name> <ip> <port> <quorum> 此命令告诉 Sentinel 开始监控一个新的主节点,指定其名称、IP、端口和仲裁数。它与 sentinel.conf 配置文件中的 sentinel monitor 配置指令相同,不同之处在于不能使用主机名作为 ip,而需要提供 IPv4 或 IPv6 地址。
  • SENTINEL REMOVE <name> 用于移除指定的主节点:该主节点将不再被监控,并会完全从 Sentinel 的内部状态中移除,因此将不再被 SENTINEL masters 等命令列出。
  • SENTINEL SET <name> [<option> <value> ...] SET 命令与 Redis 的 CONFIG SET 命令非常相似,用于更改特定主节点的配置参数。可以指定多个选项/值对(也可以不指定)。所有可以通过 sentinel.conf 配置的配置参数也可以使用 SET 命令进行配置。

以下是 SENTINEL SET 命令的一个示例,用于修改名为 objects-cache 的主节点的 down-after-milliseconds 配置

SENTINEL SET objects-cache-master down-after-milliseconds 1000

如前所述,SENTINEL SET 可用于设置所有在启动配置文件中可设置的配置参数。此外,只需使用以下命令即可更改主节点的仲裁配置,而无需先用 SENTINEL REMOVE 移除主节点,再用 SENTINEL MONITOR 重新添加

SENTINEL SET objects-cache-master quorum 5

请注意,没有等效的 GET 命令,因为 SENTINEL MASTER 以一种易于解析的格式(作为字段/值对数组)提供所有配置参数。

从 Redis 6.2 版本开始,Sentinel 还允许获取和设置全局配置参数,而在此之前这些参数仅在配置文件中支持。

  • SENTINEL CONFIG GET <name> 获取全局 Sentinel 配置参数的当前值。指定的名称可以是通配符,类似于 Redis 的 CONFIG GET 命令。
  • SENTINEL CONFIG SET <name> <value> 设置全局 Sentinel 配置参数的值。

可操作的全局参数包括

添加或移除 Sentinel

由于 Sentinel 实现的自动发现机制,向部署添加新的 Sentinel 是一个简单的过程。你只需要启动配置为监控当前活动主节点的新 Sentinel 即可。在 10 秒内,新 Sentinel 将获取其他 Sentinel 的列表以及连接到主节点的副本集合。

如果需要一次添加多个 Sentinel,建议一个接一个地添加,在添加下一个之前等待所有其他 Sentinel 都已经知道第一个 Sentinel。这有助于确保在添加新 Sentinel 的过程中发生故障时,多数仍然只能在分区的一侧实现。

通过在没有网络分区的情况下,每添加一个新 Sentinel 间隔 30 秒,可以轻松实现这一点。

在流程结束时,可以使用命令 SENTINEL MASTER mastername 来检查所有 Sentinel 是否都对监控该主节点的 Sentinel 总数达成一致。

移除 Sentinel 稍微复杂一些:Sentinel 从不忘记已经见过的 Sentinel,即使它们长时间不可访问,因为我们不想动态改变授权故障转移和创建新配置号所需的大多数。因此,在没有网络分区的情况下,应执行以下步骤来移除 Sentinel

  1. 停止要移除的 Sentinel 进程。
  2. 向所有其他 Sentinel 实例发送 SENTINEL RESET * 命令(如果你只想重置单个主节点,可以使用精确的主节点名称代替 *)。一个接一个地发送,实例之间至少等待 30 秒。
  3. 通过检查每个 Sentinel 的 SENTINEL MASTER mastername 命令的输出,确认所有 Sentinel 都对当前活动 Sentinel 的数量达成一致。

移除旧主节点或不可访问的副本

Sentinel 从不忘记给定主节点的副本,即使它们长时间不可访问。这很有用,因为 Sentinel 应该能够在网络分区或故障事件后正确地重新配置返回的副本。

此外,在故障转移后,发生故障转移的主节点实际上被添加为新主节点的副本,这样一旦它再次可用,它将被重新配置为与新主节点进行复制。

然而,有时你可能想从 Sentinel 监控的副本列表中永久移除某个副本(这可能是旧的主节点)。

为此,你需要向所有 Sentinel 发送 SENTINEL RESET mastername 命令:它们将在接下来的 10 秒内刷新副本列表,仅添加那些在当前主节点的 INFO 输出中列为正常复制的副本。

Pub/Sub 消息

客户端可以将 Sentinel 用作与 Redis 兼容的 Pub/Sub 服务器(但不能使用 PUBLISH),以便 SUBSCRIBEPSUBSCRIBE 到频道并获取特定事件的通知。

频道名称与事件名称相同。例如,名为 +sdown 的频道将接收与实例进入 SDOWN 状态相关的所有通知(SDOWN 意味着从你查询的 Sentinel 的角度来看,该实例已不可访问)。

要获取所有消息,只需使用 PSUBSCRIBE * 进行订阅。

以下是你可以使用此 API 接收的频道和消息格式列表。第一个词是频道/事件名称,其余部分是数据的格式。

注意:指定 instance details 的地方表示提供了以下参数来标识目标实例

<instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port>

标识主节点的部分(从 @ 参数到末尾)是可选的,仅当实例本身不是主节点时才指定。

  • +reset-master <instance details> -- 主节点已被重置。
  • +slave <instance details> -- 检测到一个新副本并已连接。
  • +failover-state-reconf-slaves <instance details> -- 故障转移状态变为 reconf-slaves 状态。
  • +failover-detected <instance details> -- 检测到由另一个 Sentinel 或任何其他外部实体启动的故障转移(一个连接的副本变成了主节点)。
  • +slave-reconf-sent <instance details> -- 领导 Sentinel 向该实例发送了 REPLICAOF 命令,以便将其重新配置为新的副本。
  • +slave-reconf-inprog <instance details> -- 正在重新配置的副本显示为新主节点 IP:端口对的副本,但同步过程尚未完成。
  • +slave-reconf-done <instance details> -- 副本现在已与新主节点同步。
  • -dup-sentinel <instance details> -- 指定主节点的一个或多个 Sentinel 被移除,因为它们是重复的(例如,当一个 Sentinel 实例重启时会发生这种情况)。
  • +sentinel <instance details> -- 检测到该主节点的一个新 Sentinel 并已连接。
  • +sdown <instance details> -- 指定的实例现在处于主观宕机 (Subjectively Down) 状态。
  • -sdown <instance details> -- 指定的实例不再处于主观宕机状态。
  • +odown <instance details> -- 指定的实例现在处于客观宕机 (Objectively Down) 状态。
  • -odown <instance details> -- 指定的实例不再处于客观宕机状态。
  • +new-epoch <instance details> -- 当前 epoch 已更新。
  • +try-failover <instance details> -- 新的故障转移正在进行中,等待多数投票选举。
  • +elected-leader <instance details> -- 赢得了指定 epoch 的选举,可以执行故障转移。
  • +failover-state-select-slave <instance details> -- 新的故障转移状态为 select-slave:我们正在尝试找到合适的副本进行提升。
  • no-good-slave <instance details> -- 没有合适的副本可以提升。目前我们会在一段时间后重试,但这可能会改变,并且状态机在这种情况下可能会完全中止故障转移。
  • selected-slave <instance details> -- 我们找到了指定的适合提升的副本。
  • failover-state-send-slaveof-noone <instance details> -- 我们正在尝试将提升的副本重新配置为主节点,等待其切换完成。
  • failover-end-for-timeout <instance details> -- 故障转移因超时而终止,副本最终无论如何都会被配置为与新的主节点进行复制。
  • failover-end <instance details> -- 故障转移成功终止。所有副本似乎都已重新配置为与新的主节点进行复制。
  • switch-master <master name> <oldip> <oldport> <newip> <newport> -- 配置更改后,主节点新的 IP 和地址就是指定的那个。这是大多数外部用户感兴趣的消息
  • +tilt -- 进入 Tilt 模式。
  • -tilt -- 退出 Tilt 模式。

处理 -BUSY 状态

当 Lua 脚本运行时间超过配置的 Lua 脚本时间限制时,Redis 实例会返回 -BUSY 错误。当在触发故障转移之前发生这种情况时,Redis Sentinel 会尝试发送 SCRIPT KILL 命令,该命令只有在脚本是只读的情况下才会成功。

如果在此尝试后实例仍处于错误状态,它最终将进行故障转移。

副本优先级

Redis 实例有一个配置参数叫做 replica-priority。Redis 副本实例在其 INFO 输出中暴露此信息,Sentinel 使用此信息在可用于故障转移主节点的副本中选择一个

  1. 如果副本优先级设置为 0,则该副本永远不会被提升为主节点。
  2. Sentinel 偏好优先级数字较低的副本。

例如,如果当前主节点位于同一个数据中心有一个副本 S1,在另一个数据中心有另一个副本 S2,可以将 S1 的优先级设置为 10,S2 的优先级设置为 100,这样当主节点故障且 S1 和 S2 都可用时,Sentinel 会优先选择 S1。

有关副本选择方式的更多信息,请查看本文档的 副本选择和优先级 部分

Sentinel 和 Redis 认证

当主节点配置为要求客户端进行身份验证时,作为一种安全措施,副本也需要知道凭据,以便与主节点进行身份验证并建立用于异步复制协议的主副本连接。

Redis 访问控制列表认证

从 Redis 6 开始,用户身份验证和权限通过 访问控制列表 (ACL) 进行管理。

为了让 Sentinel 在 Redis 服务器实例配置了 ACL 时能够连接,Sentinel 配置必须包含以下指令

sentinel auth-user <master-name> <username>
sentinel auth-pass <master-name> <password>

其中 <username><password> 是访问该组实例的用户名和密码。这些凭据应在所有组内的 Redis 实例上配置,并赋予最小控制权限。例如

127.0.0.1:6379> ACL SETUSER sentinel-user ON >somepassword allchannels +multi +slaveof +ping +exec +subscribe +config|rewrite +role +publish +info +client|setname +client|kill +script|kill

Redis 仅密码认证

在 Redis 6 之前,身份验证通过以下配置指令实现

  • 主节点中的 requirepass,用于设置认证密码,并确保实例不会处理未经认证的客户端请求。
  • 副本中的 masterauth,以便副本能够与主节点进行身份验证,从而正确地复制数据。

使用 Sentinel 时,不存在单个主节点的概念,因为在故障转移后,副本可能扮演主节点的角色,而旧的主节点可以被重新配置为副本。因此,您需要做的是在所有实例(包括主节点和副本)中设置上述指令。

这通常也是一个合理的设置,因为您不希望只保护主节点的数据,而副本中的相同数据却可以访问。

然而,在不常见的情况下,如果您需要一个无需身份验证即可访问的副本,您仍然可以这样做:设置一个优先级为零的副本,以防止此副本被提升为主节点,并在该副本中仅配置 masterauth 指令,而不使用 requirepass 指令,这样未经身份验证的客户端也可以读取数据。

为了让 Sentinel 在 Redis 服务器实例配置了 requirepass 时能够连接,Sentinel 配置必须包含 sentinel auth-pass 指令,格式如下

sentinel auth-pass <master-name> <password>

使用身份验证配置 Sentinel 实例

Sentinel 实例本身可以通过要求客户端使用 AUTH 命令进行身份验证来保护。从 Redis 6.2 开始,访问控制列表 (ACL) 可用,而以前的版本(从 Redis 5.0.1 开始)支持仅密码认证。

请注意,Sentinel 的身份验证配置应该应用于部署中的每个实例,并且所有实例应该使用相同的配置。此外,不应同时使用 ACL 和仅密码认证。

Sentinel 访问控制列表认证

使用 ACL 保护 Sentinel 实例的第一步是防止任何未经授权的访问。为此,您需要禁用默认超级用户(或至少为其设置一个强密码),并创建一个新用户,并允许其访问 Pub/Sub 频道

127.0.0.1:5000> ACL SETUSER admin ON >admin-password allchannels +@all
OK
127.0.0.1:5000> ACL SETUSER default off
OK

Sentinel 使用默认用户连接到其他实例。您可以使用以下配置指令提供另一个超级用户的凭据

sentinel sentinel-user <username>
sentinel sentinel-pass <password>

其中 <username><password> 分别是 Sentinel 的超级用户和密码(例如,上面示例中的 adminadmin-password)。

最后,对于传入的客户端连接进行身份验证,您可以创建一个受限的 Sentinel 用户配置文件,例如以下内容

127.0.0.1:5000> ACL SETUSER sentinel-user ON >user-password -@all +auth +client|getname +client|id +client|setname +command +hello +ping +role +sentinel|get-master-addr-by-name +sentinel|master +sentinel|myid +sentinel|replicas +sentinel|sentinels +sentinel|masters

有关更多信息,请参阅您选择的 Sentinel 客户端的文档。

Sentinel 仅密码认证

要使用仅密码认证配置 Sentinel,请在所有 Sentinel 实例中添加 requirepass 配置指令,如下所示

requirepass "your_password_here"

这样配置后,Sentinel 会做两件事

  1. 客户端向 Sentinel 发送命令时将需要密码。这很明显,因为这是此类配置指令在 Redis 中的一般工作方式。
  2. 此外,配置用于访问本地 Sentinel 的相同密码将由该 Sentinel 实例用于对其连接到的所有其他 Sentinel 实例进行身份验证。

这意味着您必须在所有 Sentinel 实例中配置相同的 requirepass 密码。这样,每个 Sentinel 都可以与其他所有 Sentinel 通信,而无需为每个 Sentinel 配置访问所有其他 Sentinel 的密码,那将非常不切实际。

使用此配置之前,请确保您的客户端库可以将 AUTH 命令发送到 Sentinel 实例。

Sentinel 客户端实现


Sentinel 需要显式的客户端支持,除非系统配置为执行脚本,将所有请求透明地重定向到新的主实例(虚拟 IP 或其他类似系统)。客户端库实现的议题在文档 Sentinel 客户端指南 中有所涵盖。

更高级的概念

在以下章节中,我们将介绍 Sentinel 工作原理的一些细节,但不会深入到本文档最后部分将涵盖的实现细节和算法。

SDOWN 和 ODOWN 故障状态

Redis Sentinel 有两种不同的“宕机”概念,一种称为*主观宕机*状态 (SDOWN),这是某个 Sentinel 实例本地的宕机状态。另一种称为*客观宕机*状态 (ODOWN),当足够多的 Sentinel(至少是监控主节点配置的 quorum 参数指定的数量)处于 SDOWN 状态,并且通过 SENTINEL is-master-down-by-addr 命令从其他 Sentinel 获取反馈时,就达到了 ODOWN 状态。

从一个 Sentinel 的角度来看,当它在配置中指定的 is-master-down-after-milliseconds 参数所指定的时间(秒)内没有收到 PING 请求的有效回复时,就达到了 SDOWN 状态。

对 PING 的可接受回复是以下之一

  • PING 回复 +PONG。
  • PING 回复 -LOADING 错误。
  • PING 回复 -MASTERDOWN 错误。

任何其他回复(或根本没有回复)都被认为是无效的。但请注意,在 INFO 输出中将自己 اعلان 为副本的逻辑主节点被认为是宕机的

请注意,SDOWN 要求在整个配置的时间间隔内没有收到可接受的回复,因此例如,如果时间间隔为 30000 毫秒(30 秒),而我们每 29 秒收到一次可接受的 ping 回复,则该实例被认为是工作的。

SDOWN 不足以触发故障转移:它仅表示单个 Sentinel 认为 Redis 实例不可用。要触发故障转移,必须达到 ODOWN 状态。

从 SDOWN 切换到 ODOWN 不使用强一致性算法,而只是一种流言传播形式:如果给定的 Sentinel 在给定的时间范围内从足够多的 Sentinel 那里收到关于主节点不工作的报告,SDOWN 将被提升为 ODOWN。如果此确认后来丢失,该标志将被清除。

需要使用实际多数投票的更严格授权才能真正启动故障转移,但未达到 ODOWN 状态时无法触发故障转移。

ODOWN 状态仅适用于主节点。对于其他类型的实例,Sentinel 不需要采取行动,因此副本和其他 Sentinel 永远不会达到 ODOWN 状态,只有 SDOWN 状态。

然而,SDOWN 也具有语义含义。例如,处于 SDOWN 状态的副本不会被执行故障转移的 Sentinel 选择进行提升。

Sentinel 和副本的自动发现

Sentinel 与其他 Sentinel 保持连接,以便互相检查可用性并交换消息。但是,您无需在运行的每个 Sentinel 实例中配置其他 Sentinel 的地址列表,因为 Sentinel 利用 Redis 实例的 Pub/Sub 功能来发现监控相同主节点和副本的其他 Sentinel。

此功能通过向名为 __sentinel__:hello 的频道发送hello 消息来实现。

类似地,您无需配置附加到主节点的副本列表,因为 Sentinel 将通过查询 Redis 自动发现此列表。

  • 每个 Sentinel 每两秒向每个被监控的主节点和副本的 Pub/Sub 频道 __sentinel__:hello 发布一条消息,宣布其存在,消息包含 IP、端口和 runid。
  • 每个 Sentinel 都订阅了每个主节点和副本的 Pub/Sub 频道 __sentinel__:hello,寻找未知的 Sentinel。当检测到新的 Sentinel 时,它们会被添加为此主节点的 Sentinel。
  • hello 消息还包含主节点的完整当前配置。如果接收到消息的 Sentinel 对于给定主节点的配置比收到的配置旧,它会立即更新为新配置。
  • 在向主节点添加新的 Sentinel 之前,一个 Sentinel 总是检查是否已经存在具有相同 runid 或相同地址(IP 和端口对)的 Sentinel。在这种情况下,所有匹配的 Sentinel 都会被移除,然后添加新的 Sentinel。

Sentinel 在故障转移过程之外对实例的重新配置

即使没有进行故障转移,Sentinel 也总是会尝试在被监控的实例上设置当前配置。具体来说

  • 根据当前配置声称自己是主节点的副本,将被配置为与当前主节点进行复制的副本。
  • 连接到错误主节点的副本将被重新配置为与正确的主节点进行复制。

对于 Sentinel 重新配置副本,必须观察到错误配置一段时间,该时间应大于用于广播新配置的周期。

这可以防止配置过时的 Sentinel(例如,它们刚刚从分区中重新加入)在接收到更新之前尝试更改副本配置。

另外请注意,始终尝试强制执行当前配置的语义如何使故障转移对分区更具弹性

  • 发生故障转移的主节点在重新可用时会被重新配置为副本。
  • 在分区期间被隔离的副本一旦可达就会被重新配置。

本节需要记住的重要一点是:Sentinel 是一个系统,其中每个进程总是会尝试将最后一个逻辑配置强加给被监控的实例集

副本选择和优先级

当一个 Sentinel 实例准备好执行故障转移时,因为主节点处于 ODOWN 状态,并且该 Sentinel 从已知的大多数 Sentinel 实例获得了故障转移的授权,就需要选择一个合适的副本。

副本选择过程会评估副本的以下信息

  1. 与主节点的断开连接时间。
  2. 副本优先级。
  3. 已处理的复制偏移量。
  4. Run ID。

发现与主节点断开连接的时间超过配置的主节点超时时间(down-after-milliseconds 选项)的十倍,再加上执行故障转移的 Sentinel 看来主节点不可用的时间,则该副本被认为不适合故障转移并被跳过。

更严格地说,如果副本的 INFO 输出表明它与主节点的断开连接时间超过了

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

则被认为不可靠并完全忽略。

副本选择仅考虑通过上述测试的副本,并按以下顺序根据上述标准对其进行排序。

  1. 根据 Redis 实例的 redis.conf 文件中配置的 replica-priority 对副本进行排序。优先级较低的副本将被优先选择。
  2. 如果优先级相同,则检查副本已处理的复制偏移量,并选择从主节点接收更多数据的副本。
  3. 如果多个副本具有相同的优先级并处理了来自主节点的相同数据,则会进行进一步检查,选择具有字典序上较小的 run ID 的副本。较低的 run ID 对副本本身没有实际优势,但有助于使副本选择过程更具确定性,而不是随机选择一个副本。

在大多数情况下,replica-priority 不需要显式设置,因此所有实例都将使用相同的默认值。如果存在特定的故障转移偏好,则必须在所有实例(包括主节点)上设置 replica-priority,因为主节点在未来的某个时间点可能成为副本,届时它将需要适当的 replica-priority 设置。

可以将 Redis 实例配置为特殊的 replica-priority 零,以使其永远不会被 Sentinel 选作新的主节点。但是,以这种方式配置的副本仍将在故障转移后由 Sentinel 重新配置,以复制新的主节点,唯一的区别是它永远不会自己成为主节点。

算法和内部原理

在以下部分中,我们将探讨 Sentinel 行为的详细信息。用户并不需要严格了解所有细节,但深入了解 Sentinel 有助于更有效地部署和操作 Sentinel。

仲裁(Quorum)

前几节提到,Sentinel 监控的每个主节点都与配置的仲裁(quorum)相关联。它指定了需要达成一致意见的 Sentinel 进程数量,这些进程认为主节点不可达或处于错误状态,才能触发故障转移。

但是,在故障转移被触发后,为了实际执行故障转移,至少需要大多数 Sentinel 授权该 Sentinel 进行故障转移。Sentinel 绝不会在仅存在少数 Sentinel 的分区中执行故障转移。

让我们试着让事情更清楚一些

  • 仲裁(Quorum):需要检测到错误状态才能将主节点标记为 ODOWN 的 Sentinel 进程数量。
  • 故障转移由 ODOWN 状态触发。
  • 故障转移一旦触发,尝试进行故障转移的 Sentinel 需要向大多数 Sentinel(如果仲裁数大于大多数,则需要超过大多数)请求授权。

这种差异可能看起来微妙,但实际上很容易理解和使用。例如,如果你有 5 个 Sentinel 实例,并且仲裁设置为 2,那么只要有 2 个 Sentinel 认为主节点不可达,就会触发故障转移。但是,其中一个 Sentinel 只有在获得至少 3 个 Sentinel 的授权后才能执行故障转移。

如果仲裁配置为 5,则所有 Sentinel 都必须就主节点错误状态达成一致,并且需要所有 Sentinel 的授权才能进行故障转移。

这意味着仲裁可以通过两种方式调整 Sentinel:

  1. 如果将仲裁设置为小于我们部署的大多数 Sentinel 的值,我们实际上是使 Sentinel 对主节点故障更敏感,即使只有少数 Sentinel 无法与主节点通信时也会立即触发故障转移。
  2. 如果将仲裁设置为大于大多数 Sentinel 的值,我们则让 Sentinel 只有在有大量(多于大多数)连接良好的 Sentinel 都同意主节点已下线时才能执行故障转移。

配置世代(Configuration epochs)

Sentinel 需要获得大多数授权才能开始故障转移,原因有几个重要方面:

当一个 Sentinel 被授权时,它会获得一个针对正在进行故障转移的主节点的唯一配置世代(configuration epoch)。这个数字将用于对故障转移完成后形成的新配置进行版本控制。由于大多数 Sentinel 同意将某个版本分配给某个 Sentinel,因此没有其他 Sentinel 能够使用它。这意味着每次故障转移的每个配置都使用唯一的版本进行版本控制。我们将看到这为何如此重要。

此外,Sentinel 有一个规则:如果一个 Sentinel 对某个主节点的故障转移投票给了另一个 Sentinel,它将等待一段时间,然后才会尝试再次对同一个主节点进行故障转移。这个延迟是你可以在 sentinel.conf 中配置的 2 * failover-timeout。这意味着 Sentinel 不会同时尝试对同一个主节点进行故障转移,第一个请求授权的 Sentinel 会尝试,如果失败,另一个会在一段时间后尝试,以此类推。

Redis Sentinel 保证了一种活性(liveness)属性:如果大多数 Sentinel 能够互相通信,那么如果主节点下线,最终会有一个 Sentinel 被授权进行故障转移。

Redis Sentinel 还保证了一种安全性(safety)属性:每个 Sentinel 都将使用不同的配置世代(configuration epoch)对同一个主节点进行故障转移。

配置传播(Configuration propagation)

一旦一个 Sentinel 成功完成主节点的故障转移,它将开始广播新的配置,以便其他 Sentinel 更新关于给定主节点的信息。

要使故障转移被视为成功,需要 Sentinel 能够向选定的副本发送 REPLICAOF NO ONE 命令,并且稍后在新主节点的 INFO 输出中观察到其角色切换为主节点。

此时,即使副本的重新配置仍在进行中,故障转移也被视为成功,并且所有 Sentinel 都必须开始报告新的配置。

传播新配置的方式就是我们需要每个 Sentinel 故障转移都使用不同的版本号(配置世代)进行授权的原因。

每个 Sentinel 不断地使用 Redis Pub/Sub 消息广播其关于主节点的配置版本,包括主节点和所有副本。同时,所有 Sentinel 都在等待消息,以了解其他 Sentinel 正在宣传的配置。

配置通过 __sentinel__:hello Pub/Sub 通道进行广播。

由于每个配置都有不同的版本号,更高的版本总是优先于较低的版本。

因此,例如,主节点 mymaster 的配置开始时,所有 Sentinel 都认为主节点位于 192.168.1.50:6379。这个配置的版本是 1。过了一段时间,一个 Sentinel 被授权使用版本 2 进行故障转移。如果故障转移成功,它将开始广播一个新的配置,例如 192.168.1.50:9000,版本为 2。所有其他实例将看到此配置并相应地更新它们的配置,因为新配置具有更高的版本。

这意味着 Sentinel 保证了第二个活性属性:一组能够互相通信的 Sentinel 最终将全部收敛到具有更高版本号的相同配置。

基本上,如果网络被分区,每个分区将收敛到本地可用的更高配置。在没有分区的特殊情况下,只有一个分区,并且所有 Sentinel 将就配置达成一致。

分区下的数据一致性

Redis Sentinel 配置是最终一致的,因此每个分区将收敛到可用的更高配置。然而,在实际使用 Sentinel 的系统中,有三个不同的参与者:

  • Redis 实例。
  • Sentinel 实例。
  • 客户端。

为了定义系统的行为,我们必须考虑这三个方面。

下面是一个简单的网络,其中有 3 个节点,每个节点都运行着一个 Redis 实例和一个 Sentinel 实例。

            +-------------+
            | Sentinel 1  |----- Client A
            | Redis 1 (M) |
            +-------------+
                    |
                    |
+-------------+     |          +------------+
| Sentinel 2  |-----+-- // ----| Sentinel 3 |----- Client B
| Redis 2 (S) |                | Redis 3 (M)|
+-------------+                +------------+

在这个系统中,初始状态是 Redis 3 是主节点,而 Redis 1 和 2 是副本。发生了一个分区,隔离了旧的主节点。Sentinel 1 和 2 开始故障转移,将 Sentinel 1 提升为新的主节点。

Sentinel 的属性保证了 Sentinel 1 和 2 现在拥有新的主节点配置。然而,Sentinel 3 由于位于不同的分区,仍然拥有旧的配置。

我们知道 Sentinel 3 的配置将在网络分区恢复时得到更新,但是如果在分区期间,有一些客户端与旧主节点一起被隔离了会发生什么?

客户端仍将能够写入旧主节点 Redis 3。当分区重新连接时,Redis 3 将变成 Redis 1 的副本,并且在分区期间写入的所有数据将丢失。

根据你的配置,你可能希望或不希望发生这种情况

  • 如果你将 Redis 用作缓存,那么客户端 B 仍然能够写入旧主节点(即使其数据会丢失)可能会很方便。
  • 如果你将 Redis 用作存储,这就不好了,你需要配置系统以部分防止此问题。

由于 Redis 是异步复制的,因此在这种情况下无法完全防止数据丢失,但是你可以使用以下 Redis 配置选项来限制 Redis 3 和 Redis 1 之间的差异:

min-replicas-to-write 1
min-replicas-max-lag 10

使用上述配置(请参阅 Redis 分发包中自注释的 redis.conf 示例以获取更多信息),一个 Redis 实例在充当主节点时,如果无法写入至少 1 个副本节点,将停止接受写入。由于复制是异步的,无法写入实际上意味着该副本节点要么已断开连接,要么在超过指定的 max-lag 秒数后未向我们发送异步确认。

使用此配置,上述示例中的 Redis 3 将在 10 秒后变得不可用。当分区恢复时,Sentinel 3 的配置将收敛到新的配置,并且客户端 B 将能够获取有效的配置并继续。

总的来说,Redis + Sentinel 作为一个整体是一个最终一致的系统,其合并函数是最后一次故障转移获胜,旧主节点的数据被丢弃以复制当前主节点的数据,因此总是存在丢失已确认写入的窗口。这是由于 Redis 的异步复制以及系统“虚拟”合并函数的丢弃性质。请注意,这并不是 Sentinel 本身的限制,即使你使用强一致性复制状态机来协调故障转移,同样的属性仍然适用。避免丢失已确认写入只有两种方法:

  1. 使用同步复制(以及适当的共识算法来运行复制状态机)。
  2. 使用一个最终一致的系统,其中可以合并同一对象的不同版本。

Redis 目前无法使用上述任何一种系统,并且这目前不在其开发目标之内。然而,有一些代理在 Redis 存储之上实现了“2”方案,例如 SoundCloud 的 Roshi 或 Netflix 的 Dynomite

Sentinel 持久化状态

Sentinel 的状态保存在 sentinel 配置文件中。例如,每次收到或创建一个新的主节点配置(由领导 Sentinel 创建)时,该配置以及配置世代都会持久化到磁盘。这意味着停止和重新启动 Sentinel 进程是安全的。

TILT 模式

Redis Sentinel 非常依赖于计算机时间:例如,为了判断一个实例是否可用,它会记住最新一次成功回复 PING 命令的时间,并将其与当前时间进行比较,以了解其间隔多久。

然而,如果计算机时间发生意外变化,或者计算机非常繁忙,或者进程因某种原因被阻塞,Sentinel 可能会开始表现出意外行为。

TILT 模式是 Sentinel 在检测到可能降低系统可靠性的异常情况时可以进入的一种特殊“保护”模式。Sentinel 的定时器中断通常每秒调用 10 次,因此我们预计两次定时器中断调用之间大约会经过 100 毫秒。

Sentinel 会记录上次定时器中断调用的时间,并将其与当前调用进行比较:如果时间差为负或异常大(2 秒或更多),则进入 TILT 模式(或者如果已经进入,则推迟退出 TILT 模式)。

进入 TILT 模式后,Sentinel 将继续监控所有情况,但:

  • 它完全停止执行任何操作。
  • 它开始对 SENTINEL is-master-down-by-addr 请求回复否定,因为检测故障的能力已不再可信。

如果一切看起来正常 30 秒,则退出 TILT 模式。

在 Sentinel 的 TILT 模式下,如果我们发送 INFO 命令,可能会得到以下响应:

$ redis-cli -p 26379
127.0.0.1:26379> info
(Other information from Sentinel server skipped.)

# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=0,sentinels=1

字段 "sentinel_tilt_since_seconds" 表示 Sentinel 进入 TILT 模式已持续多少秒。如果未处于 TILT 模式,则该值为 -1。

请注意,在某种程度上,TILT 模式可以使用许多内核提供的单调时钟 API 替代。然而,目前尚不清楚这是否是一个好的解决方案,因为当前的系统可以避免进程被暂停或长时间未被调度器执行时出现的问题。

关于本手册页中使用的“slave”一词的说明:从 Redis 5 开始,如果不是为了向后兼容,Redis 项目不再使用“slave”一词。不幸的是,在此命令中,“slave”一词是协议的一部分,因此只有当此 API 自然弃用时,我们才能删除此类用法。

评价此页面
回到顶部 ↑