使用Redis Sentinel实现高可用性

非集群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. 当多个哨兵同意某个主节点不再可用时,会执行故障检测。这降低了误报的概率。
  2. 即使并非所有的Sentinel进程都在工作,Sentinel仍然能够工作,这使得系统在面对故障时具有鲁棒性。毕竟,拥有一个本身就是单点故障的故障转移系统是没有意义的。

Sentinels、Redis实例(主节点和副本节点)以及连接到Sentinel和Redis的客户端的总和,也是一个具有特定属性的更大的分布式系统。在本文档中,概念将逐步引入,从理解Sentinel基本属性所需的基本信息开始,到更复杂的信息(这些是可选的),以便理解Sentinel的确切工作原理。

Sentinel 快速开始

获取Sentinel

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

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

新的开发在不稳定分支中进行,新功能有时会在被认为稳定后立即回迁到最新的稳定分支。

Redis Sentinel 版本 1,随 Redis 2.6 发布,已弃用,不应再使用。

运行Sentinel

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

redis-sentinel /path/to/sentinel.conf

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

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

两种方式效果相同。

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

默认情况下,哨兵运行监听TCP端口26379的连接,因此 为了让哨兵正常工作,您的服务器的端口26379必须开放以接收 来自其他哨兵实例IP地址的连接。 否则,哨兵无法通信并无法就采取何种行动达成一致,因此故障转移 将永远不会执行。

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

  1. 为了确保部署的健壮性,您至少需要三个Sentinel实例。
  2. 三个Sentinel实例应放置在被认为会以独立方式失败的计算机或虚拟机中。例如,不同的物理服务器或在不同可用区执行的虚拟机。
  3. Sentinel + Redis 分布式系统不保证在故障期间保留已确认的写入,因为 Redis 使用异步复制。然而,有一些部署 Sentinel 的方法可以使写入丢失的时间窗口限制在某些时刻,而其他部署方式则不太安全。
  4. 您的客户端需要支持Sentinel。流行的客户端库支持Sentinel,但并非所有都支持。
  5. 如果你不经常在开发环境中测试,或者更好的是,如果可能的话,在生产环境中测试它们是否正常工作,那么没有高可用性(HA)设置是安全的。你可能有一个配置错误,只有在为时已晚时(比如凌晨3点当你的主服务器停止工作时)才会变得明显。
  6. Sentinel、Docker 或其他形式的网络地址转换或端口映射应谨慎混合使用: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。一切都很明显,除了quorum参数:

  • quorum 是需要达成一致的哨兵数量,以便真正将主服务器标记为故障,并在可能的情况下最终启动故障转移程序。
  • 然而法定人数仅用于检测故障。为了实际执行故障转移,需要选举一个Sentinel作为故障转移的领导者,并获得授权继续执行。这只有在大多数Sentinel进程投票的情况下才会发生。

例如,如果你有5个Sentinel进程,并且为给定的主节点设置了法定人数为2,那么会发生以下情况:

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

实际上,这意味着在故障期间,如果大多数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进程等等。本节展示了一些示例部署。

我们使用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代表副本)。
  • 哨兵被称为 S1, S2, S3, ..., Sn。
  • 客户端被称为C1, C2, C3, ..., Cn。
  • 当一个实例因为Sentinel的操作而改变角色时,我们将其放在方括号内,所以[M1]表示一个因为Sentinel干预而成为主节点的实例。

请注意,我们永远不会展示仅使用两个哨兵的情况,因为哨兵总是需要与大多数进行通信才能启动故障转移。

示例 1:仅有两个哨兵,不要这样做

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

Configuration: quorum = 1
  • 在这种设置中,如果主节点M1发生故障,R1将被提升,因为两个Sentinels可以就故障达成一致(显然,法定人数设置为1),并且也可以授权进行故障转移,因为多数是2。所以表面上看起来它可以工作,但是请查看下一点以了解为什么这种设置是有问题的。
  • 如果运行M1的盒子停止工作,S1也会停止工作。在另一个盒子S2中运行的Sentinel将无法授权故障转移,因此系统将变得不可用。

请注意,为了命令不同的故障转移,并随后将最新配置传播给所有哨兵,需要多数同意。另外请注意,在上述设置中,单方面进行故障转移的能力,没有任何协议,将是非常危险的:

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

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

所以请始终在三个不同的盒子中部署至少三个哨兵

示例 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:客户端框中的哨兵

有时我们只有两个Redis服务器可用,一个用于主服务器,一个用于副本服务器。在这种情况下,示例2中的配置不可行,因此我们可以采用以下方法,将哨兵放置在客户端所在的位置:

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

      Configuration: quorum = 2

在这种设置中,哨兵的观点与客户端相同:如果大多数客户端可以访问主服务器,那就没问题。这里的C1、C2、C3是通用客户端,并不意味着C1标识连接到Redis的单个客户端。它更像是应用程序服务器、Rails应用程序之类的东西。

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

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

因此,这是一个有效的设置,但示例2中的设置具有一些优势,例如Redis的高可用性系统与Redis本身运行在同一台机器上,这可能更易于管理,并且能够限制处于少数分区的主节点接收写入的时间。

示例 4:少于三个客户端的 Sentinel 客户端

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

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

      Configuration: quorum = 3

这与示例3中的设置类似,但在这里我们在可用的四个盒子中运行四个Sentinels。如果主M1变得不可用,其他三个Sentinels将执行故障转移。

理论上,这种设置可以通过移除运行C2和S4的盒子,并将法定人数设置为2来工作。然而,如果我们不在应用层实现高可用性,就不太可能在Redis端实现高可用性。

Sentinel、Docker、NAT 及可能的问题

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

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

重新映射端口和地址会在两个方面对Sentinel造成问题:

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

由于哨兵自动检测副本使用的是主节点的INFO输出信息,检测到的副本将无法访问,哨兵将永远无法对主节点进行故障转移,因为从系统的角度来看没有好的副本,所以目前无法使用哨兵监控一组部署在Docker中的主节点和副本实例,除非你指示Docker将端口1:1映射

对于第一个问题,如果您想使用Docker运行一组Sentinel实例并转发端口(或任何其他端口重新映射的NAT设置),您可以使用以下两个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 sentinel announce-ip

启用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 的回复,主节点将被检测为故障。

一旦你启动了三个哨兵,你会看到它们记录的一些消息,例如:

+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 提供了一个 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,这意味着多个Sentinels同意主服务器无法访问的事实。
  3. 哨兵们投票选出一个哨兵,该哨兵将开始第一次故障转移尝试。
  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 端口)。Sentinels 接受使用 Redis 协议的命令,因此您可以使用 redis-cli 或任何其他未修改的 Redis 客户端与 Sentinel 进行通信。

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

哨兵命令

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

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

为了连接管理和管理目的,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 命令。更多详情请参阅 发布/订阅消息 部分

运行时重新配置Sentinel

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

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

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

以下是一个修改名为objects-cache的主机的down-after-milliseconds配置的SENTINEL SET命令示例:

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

可以操作的全局参数包括:

添加或删除哨兵

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

如果需要一次性添加多个哨兵,建议逐个添加,等待所有其他哨兵都已经知道第一个哨兵后再添加下一个。这样做的好处是,即使在添加新哨兵的过程中发生故障,仍然可以保证多数派只能在一侧达成。

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

在过程结束时,可以使用命令 SENTINEL MASTER mastername 来检查所有哨兵是否同意监控主服务器的哨兵总数。

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

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

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

哨兵永远不会忘记给定主节点的副本,即使它们长时间无法访问。这是有用的,因为哨兵应该能够在网络分区或故障事件后正确重新配置返回的副本。

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

然而,有时您可能希望从Sentinels监控的副本列表中永久移除一个副本(可能是旧的主服务器)。

为了做到这一点,你需要向所有哨兵发送一个SENTINEL RESET mastername命令:他们将在接下来的10秒内刷新副本列表,只添加那些从当前主服务器正确复制的副本INFO输出。

发布/订阅消息

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

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

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

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

注意:当指定实例详情时,意味着提供了以下参数来识别目标实例:

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

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

  • +reset-master -- 主节点已被重置。
  • +slave -- 检测到并附加了一个新的副本。
  • +failover-state-reconf-slaves -- 故障转移状态更改为 reconf-slaves 状态。
  • +failover-detected -- 检测到由另一个Sentinel或任何其他外部实体启动的故障转移(一个附加的副本变成了主节点)。
  • +slave-reconf-sent -- 领导者哨兵向此实例发送了REPLICAOF命令,以便为新副本重新配置它。
  • +slave-reconf-inprog -- 正在重新配置的副本显示为新主节点的ip:port对的副本,但同步过程尚未完成。
  • +slave-reconf-done -- 副本现在已与新主服务器同步。
  • -dup-sentinel -- 指定的主节点的一个或多个哨兵被移除,因为它们被识别为重复(例如,当哨兵实例重新启动时会发生这种情况)。
  • +sentinel -- 检测到并附加了一个新的哨兵到这个主节点。
  • +sdown -- 指定的实例现在处于主观下线状态。
  • -sdown -- 指定的实例不再处于主观下线状态。
  • +odown -- 指定的实例现在处于客观下线状态。
  • -odown -- 指定的实例不再处于客观下线状态。
  • +new-epoch -- 当前纪元已更新。
  • +try-failover -- 新的故障转移正在进行中,等待被多数选举。
  • +elected-leader -- 赢得了指定时期的选举,可以进行故障转移。
  • +failover-state-select-slave -- 新的故障转移状态是 select-slave:我们正在尝试找到一个合适的副本进行提升。
  • no-good-slave -- 没有合适的副本可以提升。目前我们会在一段时间后重试,但这种情况可能会改变,状态机可能会完全中止故障转移。
  • selected-slave -- 我们找到了指定的良好副本进行提升。
  • failover-state-send-slaveof-noone -- 我们正在尝试将提升的副本重新配置为主节点,等待其切换。
  • failover-end-for-timeout -- 由于超时,故障转移已终止,副本最终将被配置为与新主节点进行复制。
  • failover-end -- 故障转移成功终止。所有副本似乎都已重新配置为与新主服务器进行复制。
  • switch-master -- 主服务器的新IP和地址在配置更改后为指定的IP和地址。这是大多数外部用户感兴趣的消息
  • +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都可用时,将优先选择S1。

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

Sentinel 和 Redis 认证

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

Redis 访问控制列表认证

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

为了让哨兵在配置了ACL的情况下连接到Redis服务器实例,哨兵配置必须包含以下指令:

sentinel auth-user <master-name> <username>
sentinel auth-pass <master-name> <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指令,从而使未经认证的客户端可以读取数据。

为了让哨兵在配置了requirepass时连接到Redis服务器实例,哨兵配置必须包含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>

其中分别是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,请将requirepass配置指令添加到所有你的Sentinel实例中,如下所示:

requirepass "your_password_here"

当以这种方式配置时,Sentinels 将做两件事:

  1. 客户端需要密码才能向哨兵发送命令。这是显而易见的,因为这是Redis中此类配置指令的一般工作方式。
  2. 此外,配置用于访问本地Sentinel的相同密码将被此Sentinel实例用于验证其连接到的所有其他Sentinel实例。

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

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

Sentinel 客户端实现


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

更高级的概念

在接下来的部分中,我们将介绍一些关于Sentinel如何工作的细节,而不涉及将在本文档最后部分讨论的实现细节和算法。

SDOWN 和 ODOWN 故障状态

Redis Sentinel 有两种不同的下线概念,一种称为主观下线状态(SDOWN),这是给定 Sentinel 实例本地的下线状态。另一种称为客观下线状态(ODOWN),当足够多的 Sentinel(至少是配置为被监控主节点的quorum参数的数量)处于 SDOWN 状态,并通过使用SENTINEL is-master-down-by-addr命令从其他 Sentinel 获得反馈时,就会达到这种状态。

从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 选择进行提升。

哨兵和副本自动发现

哨兵(Sentinels)保持与其他哨兵的连接,以便相互检查彼此的可用性,并交换消息。然而,您不需要在运行的每个哨兵实例中配置其他哨兵地址的列表,因为哨兵利用Redis实例的发布/订阅功能来发现监控相同主节点和副本的其他哨兵。

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

同样,你不需要配置连接到主服务器的副本列表,因为哨兵会自动通过查询Redis来发现这个列表。

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

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

即使没有进行故障转移,Sentinels 也会始终尝试在受监控的实例上设置当前配置。具体来说:

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

为了让哨兵重新配置副本,必须在一段时间内观察到错误的配置,这段时间必须大于用于广播新配置的周期。

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

还要注意,始终尝试强制实施当前配置的语义如何使故障转移更能抵抗分区:

  • 当主服务器恢复可用时,它们会被重新配置为副本。
  • 在分区期间被分区的副本一旦可达就会重新配置。

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

副本选择和优先级

当Sentinel实例准备执行故障转移时,由于主服务器处于ODOWN状态,并且Sentinel从已知的大多数Sentinel实例中获得了故障转移的授权,因此需要选择一个合适的副本。

副本选择过程评估以下关于副本的信息:

  1. 与主服务器的断开时间。
  2. 副本优先级。
  3. 已处理的复制偏移量。
  4. 运行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. 如果多个副本具有相同的优先级并且从主服务器处理了相同的数据,则进行进一步检查,选择运行ID字典序较小的副本。拥有较低的运行ID对副本来说并不是真正的优势,但为了使副本选择过程更加确定性,而不是随机选择一个副本,这是有用的。

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

可以将Redis实例配置为特殊的replica-priority为零,以便Sentinel永远不会选择它作为新的主节点。然而,以这种方式配置的副本仍然会被Sentinel重新配置,以便在故障转移后与新主节点进行复制,唯一的区别是它永远不会成为主节点本身。

算法和内部机制

在接下来的章节中,我们将探讨Sentinel行为的细节。 用户并不需要严格了解所有细节,但对Sentinel的深入理解可能有助于以更有效的方式部署和操作Sentinel。

法定人数

前面的部分展示了每个由Sentinel监控的主服务器都与一个配置的quorum相关联。它指定了需要多少个Sentinel进程同意主服务器的不可达性或错误状态,以便触发故障转移。

然而,在触发故障转移后,为了实际执行故障转移,至少需要大多数哨兵授权该哨兵进行故障转移。哨兵永远不会在存在少数哨兵的分区中执行故障转移。

让我们试着把事情说得更清楚一些:

  • Quorum: 需要检测到错误条件的Sentinel进程数量,以便将主服务器标记为ODOWN
  • 故障转移由ODOWN状态触发。
  • 一旦触发故障转移,尝试进行故障转移的Sentinel需要向大多数Sentinel(或者如果法定人数设置为大于大多数,则需要向更多的Sentinel)请求授权。

差异可能看起来微妙,但实际上非常容易理解和使用。例如,如果您有5个Sentinel实例,并且法定人数设置为2,一旦2个Sentinel认为主节点不可达,就会触发故障转移,然而,这两个Sentinel中的一个只有在获得至少3个Sentinel的授权后才能进行故障转移。

如果将法定人数配置为5,所有Sentinels必须就主节点错误条件达成一致,并且需要所有Sentinels的授权才能进行故障转移。

这意味着可以通过两种方式使用quorum来调整Sentinel:

  1. 如果将法定人数设置为小于我们部署的哨兵多数,我们基本上是在使哨兵对主节点故障更加敏感,一旦甚至只有少数哨兵无法与主节点通信,就会触发故障转移。
  2. 如果将法定人数设置为大于大多数哨兵的值,我们使得哨兵只有在有大量(大于大多数)连接良好的哨兵同意主服务器已关闭时才能进行故障转移。

配置周期

哨兵需要获得多数授权才能启动故障转移,原因如下:

当Sentinel被授权时,它会为其正在故障转移的主服务器获取一个唯一的配置纪元。这是一个数字,将在故障转移完成后用于对新配置进行版本控制。因为大多数Sentinel同意将给定的版本分配给给定的Sentinel,所以其他Sentinel将无法使用它。这意味着每次故障转移的每个配置都有一个唯一的版本。我们将看到为什么这一点如此重要。

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

Redis Sentinel 保证了活性属性,即如果大多数 Sentinel 能够通信,最终当主节点宕机时,其中一个 Sentinel 将被授权进行故障转移。

Redis 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 实例。
  • 哨兵实例。
  • 客户。

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

以下是一个简单的网络,其中有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是副本。发生了一个分区,隔离了旧的主节点。哨兵1和2开始了一个故障转移,将哨兵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配置文件中。例如,每次接收到新的配置或创建(领导者Sentinels)时,对于主节点,配置会与配置纪元一起持久化到磁盘上。这意味着停止和重新启动Sentinel进程是安全的。

TILT 模式

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

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

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

Sentinel 的作用是记录上一次计时器中断被调用的时间,并将其与当前调用进行比较:如果时间差为负数或异常大(2秒或更多),则进入 TILT 模式(如果已经进入 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自然弃用时才能删除这些出现的情况。

RATE THIS PAGE
Back to top ↑