跳转到主要内容

使用 Sentinel

KeyDB 提供了使用 Sentinel 的选项,其派生自 Redis,但由于提供了主动复制选项,Sentinel 并非必需,使用起来可能更复杂。但是,如果您正在迁移 Redis 项目,KeyDB 仍将与您的 Sentinel 实例设置配合使用。

KeyDB Sentinel 文档#

KeyDB Sentinel 为 KeyDB 提供高可用性。实际上,这意味着使用 Sentinel,您可以创建一个 KeyDB 部署,该部署可以在没有人为干预的情况下抵御某些类型的故障。

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

以下是 Sentinel 在宏观层面(即**大局**)的全部功能列表:

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

Sentinel 的分布式特性#

KeyDB Sentinel 是一个分布式系统

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

  1. 当多个 Sentinel 就某个主实例不再可用达成一致时,执行故障检测。这降低了误报的可能性。
  2. 即使并非所有 Sentinel 进程都在工作,Sentinel 也能正常工作,从而使系统能够抵御故障。毕竟,如果一个故障转移系统本身是一个单点故障,那就没什么意思了。

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

KeyDB Sentinel 规范#

介绍#

KeyDB Sentinel 是目前正在开发的 KeyDB 高可用性解决方案的名称。它与 KeyDB 集群无关,旨在供不需要 KeyDB 集群,但只需要在主实例无法正常工作时执行自动故障转移的用户使用。

简而言之,KeyDB Sentinel 将能够做到以下几点:

  • 监控主实例和副本实例,查看它们是否可用。
  • 当主实例发生故障时,将副本提升为主实例。
  • 当副本被选为新的主实例时,修改客户端配置。
  • 使用通知功能向系统管理员报告事件。

因此,KeyDB Sentinel 的三个不同角色可以概括为以下三个主要方面:

  • 监控。
  • 通知。
  • 自动故障转移。

以下文档解释了 KeyDB Sentinel 为实现这些目标而进行的设计。

KeyDB Sentinel 的理念#

KeyDB Sentinel 的理念是在网络的各个位置设置多个“监控设备”,监控 KeyDB 主实例。

然而,这些独立的设备不能在未经其他 Sentinel 同意的情况下采取行动。

一旦检测到 KeyDB 主实例出现故障,为了启动故障转移过程,Sentinel 必须验证已达到给定程度的共识。

Sentinel 的数量、它们在网络中的位置以及配置的仲裁,在许多可能性中选择所需的行为。

KeyDB Sentinel 不使用任何代理:客户端重新配置通过运行用户提供的可执行文件(例如 shell 脚本或 Python 程序)以用户设置的特定方式执行。

将以何种形式发布#

KeyDB Sentinel 只是 keydb-server 可执行文件的一种特殊模式。

如果 keydb-server 在 argv[0] 中以 "keydb-sentinel" 调​​用(例如使用符号链接或复制文件),或者如果传递了 --sentinel 选项,则 KeyDB 实例将以 sentinel 模式启动,并且只理解与 sentinel 相关的命令。所有其他命令都将被拒绝。

Sentinel 的整个实现将位于一个独立的 sentinel.c 文件中,对代码库的其余部分影响最小。然而,这种解决方案允许使用 KeyDB 中已实现的所有功能,而无需重新实现它们或为 KeyDB Sentinel 维护一个独立的 codebase。

Sentinels 网络#

所有 Sentinel 都会与以下对象建立持久连接:

  • 受监控的主实例。
  • 所有其副本,这些副本通过主实例的 INFO 输出发现。
  • 所有连接到此主实例的其他 Sentinel,通过 Pub/Sub 发现。

Sentinels 使用 KeyDB 协议相互通信,并回复外部客户端。

KeyDB Sentinels 导出一个 SENTINEL 命令。SENTINEL 命令的子命令用于执行不同的操作。

例如,SENTINEL masters 命令枚举所有受监控的主实例及其状态。然而,Sentinels 也可以像正常的 KeyDB 实例一样回复 PING 命令,因此可以将其视为正常的 KeyDB 实例来监控 Sentinel。

每个 sentinel 执行的网络任务列表如下:

  • 一个 Sentinel 每五秒多次使用 master Pub/Sub 发布其存在。
  • 一个 Sentinel 使用 TCP 端口接受命令。默认端口为 26379。
  • 一个 Sentinel 通过发送 PING 命令持续监控主实例、副本实例和其他 Sentinel。
  • 一个 Sentinel 每十秒向主实例和副本实例发送 INFO 命令,以获取连接副本的新列表、主实例的状态等。
  • 一个 Sentinel 监控 Sentinel Pub/Sub "hello" 频道,以发现新连接的 Sentinel,或检测不再连接的 Sentinel。使用的频道是 __sentinel__:hello

Sentinel 发现#

为了使 sentinel 的配置尽可能简单,每个 sentinel 都使用 KeyDB master Pub/Sub 功能广播其存在。

每个 sentinel 都订阅相同的频道,并将关于其存在的信息广播到相同的频道,包括 Sentinel 的运行 ID 以及它监听命令的 IP 地址和端口。

每个 sentinel 维护一个其他 sentinel 运行 ID、IP 和端口的列表。如果一个 sentinel 在太长时间内(假定 Master 看起来工作正常)不再使用 Pub/Sub 宣布其存在,则将其从列表中删除。在这种情况下,会向系统管理员发送通知。

故障主实例的检测#

从 KeyDB Sentinel 的角度来看,当一个实例在指定秒数内连续无法正确回复 PING 命令时,该实例即被视为不可用。

PING 回复被视为有效,以下条件之一应为真:

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

不被视为可接受的回复:

  • PING 回复 -BUSY 错误。
  • PING 回复 -MISCONF 错误。
  • PING 回复在指定毫秒数后仍未收到。

PING 不应回复除上述错误代码以外的任何错误代码,但 KeyDB Sentinel 将任何其他错误代码视为可接受的回复。

处理 -BUSY 状态#

当脚本运行时间超过配置的脚本时间限制时,会返回 -BUSY 错误。当这种情况发生时,在触发故障转移之前,KeyDB Sentinel 将尝试发送 "SCRIPT KILL" 命令,该命令仅在脚本为只读时才能成功。

主观宕机和客观宕机#

从 Sentinel 的角度来看,主实例有两种不同的错误情况:

  • 主观宕机(又称 S_DOWN)表示主实例从 Sentinel 的角度来看是宕机的。
  • 客观宕机(又称 O_DOWN)表示主实例从足够多的 Sentinel 的角度来看是主观宕机的,以达到该主实例配置的法定人数。

Sentinel 如何同意将主实例标记为 O_DOWN#

一旦 Sentinel 检测到主实例处于 S_DOWN 状态,它就开始每秒向其他 Sentinel 发送 SENTINEL is-master-down-by-addr 请求。回复存储在每个 Sentinel 内存中的状态中。

一个 Sentinel 每秒扫描状态十次,并检查是否有足够的 Sentinel 认为主实例已宕机(这并非此操作特有,大多数状态检查都以这种频率执行)。

如果此 Sentinel 已为此主实例处于 S_DOWN 状态,并且有足够的其他 Sentinel 最近报告了此状态(当前有效期设置为 5 秒),则主实例被标记为 O_DOWN (客观宕机)。

请注意,O_DOWN 状态不会在 Sentinel 之间传播。每个 Sentinel 都可以独立达到此状态。

SENTINEL is-master-down-by-addr 命令#

Sentinels 使用 SENTINEL is-master-down-by-addr 命令询问其他 Sentinels 关于主实例从它们本地角度的状态。此命令以布尔值(以 0 或 1 整数回复的形式,作为多批量回复的第一个元素)回复。

然而,为了避免误报,该命令以以下方式工作:

  • 如果指定的 IP 和端口未知,则返回 0。
  • 如果找到指定的 IP 和端口但不属于主实例,则返回 0。
  • 如果 Sentinel 处于 TILT 模式(参见本文档后面),则返回 0。
  • 仅当实例已知、是主实例、被标记为 S_DOWN 且 Sentinel 处于 TILT 模式时,才返回 1。

删除重复的 Sentinel#

为了达到配置的仲裁,我们绝对要确保仲裁是由不同的物理 Sentinel 实例达成的。在任何情况下,我们都不应该从同一个实例获得共识,因为出于某种原因,它似乎是两个或多个不同的 Sentinel 实例。

这通过积极删除重复的 Sentinel 来强制执行:每次 Sentinel 在 Hello Pub/Sub 通道中发送带有其地址和 runid 的消息时,如果我们在该主实例的 Sentinel 表中找不到完美的匹配(相同的 runid 和地址),我们就会删除任何其他具有相同 runid 或相同地址的 Sentinel。然后添加新的 Sentinel。

例如,如果一个 Sentinel 实例重新启动,Run ID 将不同,并且具有相同 IP 地址和端口对的旧 Sentinel 将被删除。

启动故障转移:领导者和观察者#

一个主实例被标记为 O_DOWN 不足以启动故障转移过程。哪个 Sentinel 应该启动故障转移也需要决定。

此外,Sentinel 可以通过两种方式配置:仅作为无法执行故障转移的监视器,或者作为可以启动故障转移的 Sentinel。

理想的情况是只有一个 Sentinel 会启动故障转移过程,并且这个 Sentinel 应该从被允许执行故障转移的 Sentinel 中选择。

在 Sentinel 中,故障转移期间有两种角色:

  • 领导者 Sentinel 是被选中执行故障转移的 Sentinel。
  • 观察者 Sentinel 是其他仅跟踪故障转移过程而不执行主动操作的 Sentinel。

因此,启动故障转移的条件是:

  • 主实例处于 O_DOWN 状态。
  • 一个 Sentinel 被选为领导者。

领导者 Sentinel 选举#

选举过程如下:

  • 每个处于 O_DOWN 状态的主实例的 Sentinel 以 10 HZ 的频率更新其内部状态,以刷新其主观领导者。

每个 sentinel 都会以这种方式选择一个主观领导者。

  • 我们所知的关于给定主服务器的每个 Sentinel,只要是可达的(没有 S_DOWN 状态),并且被允许执行故障转移(此 Sentinel 特定配置通过 Hello 频道传播),都是可能的候选者。
  • 在所有可能的候选者中,选择按字典顺序运行 ID 较小的那个。

每当一个 Sentinel 回复 MASTER is-sentinel-down-by-addr 命令时,它也会回复其主观领导者的 Run ID。

每个拥有故障主服务器(O_DOWN)的 Sentinel 以 10 HZ 的频率检查其主观领导者以及所有其他 Sentinel 的主观领导者,并且如果发生以下情况,它将自己标记为领导者:

  • 它是自己的主观领导者。
  • 至少 N-1 个其他将主实例视为宕机且可访问的 Sentinel 也认为它是领导者。N 是此主实例配置的法定人数。
  • 参与投票过程的所有 Sentinel 中(可访问且也将主实例视为宕机的),至少 50% + 1 应同意该领导者。

因此,例如,如果总共有三个 Sentinel,主实例正在发生故障,并且所有三个 Sentinel 都能通信(没有 Sentinel 发生故障),并且此主实例配置的法定人数为 2,则当它和另一个 Sentinel 都同意它是一个主观领导者时,一个 Sentinel 将认为自己是一个客观领导者。

一旦 Sentinel 检测到自己是客观领导者,它就会用 FAILOVER_IN_PROGRESSIM_THE_LEADER 标志标记主实例,并在 SENTINEL_FAILOVER_DELAY(目前为 5 秒)加上 0 毫秒到 10000 毫秒之间的随机额外时间后启动故障转移过程。

在此期间,我们以每秒一次的频率(通常是 10 秒)向所有副本请求 INFO。如果在此期间副本转换为主实例,则故障转移将暂停,并且领导者将清除 IM_THE_LEADER 标志,将自己转变为观察者。

领导者选举过程的保证#

如您所见,Sentinel 成为领导者并不严格要求多数。用户可以强制需要多数,例如,如果总共有 9 个 Sentinel,则将主仲裁设置为 5。

然而,也可以在 9 个 Sentinel 的情况下将仲裁设置为 2,以提高对网络分裂或故障 Sentinel 或其他错误条件的抵抗力。在这种情况下,对竞态条件(多个 Sentinel 同时开始执行故障转移)的保护由用于启动故障转移的随机延迟以及对副本实例的持续监控提供,以检测另一个 Sentinel(或人工)是否启动了故障转移过程。

此外,要提升的副本是使用确定性过程选择的,以最大限度地减少两个具有完整工作副本视图的不同 Sentinel 可能选择两个不同的副本进行提升的机会。

然而,很容易想象网络分裂和特定配置下,两个 Sentinel 可能同时开始作为领导者行动,在网络中无法通信的两个不同部分选举两个不同的副本作为主实例。KeyDB Sentinel 用户应该评估网络拓扑并根据其目标和不同的权衡选择适当的仲裁。

观察者如何理解故障转移已开始#

观察者只是一个不认为自己是领导者,但仍然看到主实例处于 O_DOWN 状态的 Sentinel。

观察者仍然能够根据故障转移过程中发生的情况跟踪和更新内部状态,但并不直接依赖领导者与其通信以获取进度信息。它只是观察副本的状态以了解正在发生的事情。

具体而言,如果连接到主实例的副本变为新的主实例(观察者可以在 INFO 输出中看到),观察者会将主实例标记为 FAILOVER_IN_PROGRESS。一旦所有其他可达的副本都成为该副本的副本(该副本已转换为新的主实例),观察者也会认为故障转移完成。

如果副本处于 FAILOVER_IN_PROGRESS 状态并且故障转移长时间没有进展,同时其他 Sentinel 开始声称该 Sentinel 是客观领导者(例如,因为旧的领导者不再可达),则该 Sentinel 将自己标记为 IM_THE_LEADER 并继续进行故障转移。

注意:所有 Sentinel 状态,包括主观和客观领导地位,都是一个动态过程,以 10 HZ 的频率持续刷新。Sentinel 中没有“一次性决策”步骤。

选择要提升的副本#

如果一个主实例有多个副本,则通过检查副本优先级(KeyDB 实例的一个新配置选项,通过 INFO 输出传播)来选择要提升为主实例的副本,并选择优先级值较低的副本(它是一个整数,类似于 DNS 系统的 MX 字段)。所有长时间断开与主实例连接的副本(陈旧数据)都将被丢弃。

如果存在具有相同优先级的副本,则选择按字典顺序运行 ID 较小的副本。

如果没有副本可供选择(因为所有副本都失败),则根本不会启动故障转移。但是,如果没有副本可供选择(因为主实例在监控会话中**从不**拥有副本),那么故障转移仍会执行,只是调用用户脚本。然而,要实现这一点,必须为该主实例设置一个特殊配置选项(force-failover-without-replicas)。

这很有用,因为在某些配置中,脚本可以在 IP 协议级别配置新的实例,但没有附加的副本。

故障转移过程#

故障转移过程包括以下步骤:

  • 1) 使用 REPLICAOF NO ONE 命令将选定的副本转换为新的主实例。
  • 2) 将所有剩余的副本(如果有)转换为新主实例的副本。这是逐步完成的,一个副本接一个,等待上一个副本完成同步过程后再开始下一个。
  • 3) 调用用户脚本通知客户端配置已更改。
  • 4) 将旧的故障主实例从表中完全删除,并添加具有相同名称的新主实例。

如果步骤“1”失败,则故障转移将中止。

所有其他错误都被认为是非致命的。

TILT 模式#

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

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

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

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

当处于 TILT 模式时,Sentinel 将继续监控一切,但:

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

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

Sentinel 监控其他 Sentinel#

当一个 Sentinel 长时间(超过主实例配置的超时时间 30 分钟)未通过 Pub/Sub 频道宣传自己,但同时主实例似乎工作正常时,该 Sentinel 将从该主实例的 Sentinel 表中删除,并向系统管理员发送通知。

用户提供的脚本#

Sentinel 可以选择调用用户提供的脚本来执行两项任务:

  • 通知客户端配置已更改。
  • 通知系统管理员问题。

用于通知客户端配置更改的脚本具有以下参数:

  • 调用 Sentinel 的 IP:端口。
  • 旧主实例的 IP:端口。
  • 新主实例的 IP:端口。

用于发送通知的脚本使用以下参数调用:

  • 调用 Sentinel 的 IP:端口。
  • 要传递给系统管理员的消息通过写入标准输入传递。

使用调用 Sentinel 的 IP:端口,脚本可以调用 SENTINEL 子命令以获取更多信息(如果需要)。

通知脚本的具体实现可能使用“mail”命令或某些其他命令来发送短信、电子邮件、推文。

Web 应用程序中修改配置脚本的实现可能使用 HTTP GET 请求来强制客户端更新配置,或使用特定设置中任何其他合理的机制。

设置示例#

假想设置

计算机 A 运行 KeyDB 主实例。
计算机 B 运行 KeyDB 副本和客户端软件。

在这种简单的配置中,可以在“B”上放置一个 Sentinel,并将“最小协议”设置为一(不需要其他 Sentinel 的确认)。

如果“A”失败,故障转移过程将开始,副本将被选为主实例,并且客户端软件将被重新配置。

假想设置

计算机 A 运行 KeyDB 主实例
计算机 B 运行 KeyDB 副本
计算机 C、D、E、F、G 是作为客户端的 Web 服务器

在此设置中,可以在 C、D、E、F、G 上运行五个 Sentinel,并将“最小协议”设置为 3。

在实际生产环境中,需要评估不同计算机之间的网络连接方式,并检查在网络分裂期间会发生什么,以便选择 Sentinel 的放置位置以及最小协议级别,从而使单个网络臂的故障不会触发故障转移。

一般来说,如果存在复杂的网络拓扑,最小协议应设置为同一网络臂中同时存在的 Sentinel 的最大数量加一。

SENTINEL 子命令#

  • SENTINEL masters,提供已配置主实例的列表。
  • SENTINEL replicas <master name>,提供指定名称主实例的副本列表。
  • SENTINEL sentinels <master name>,提供指定名称主实例的 Sentinel 列表。
  • SENTINEL is-master-down-by-addr <ip> <port>,返回一个包含两个元素的多批量回复,其中第一个元素是 :0 或 :1,第二个是故障转移的主观领导者。

待办事项#

  • 更详细的用户脚本错误处理规范,包括返回代码可能意味着什么,例如 0:重试。1:致命错误。2:重试,等等。
  • 更详细的用户脚本在给定时间内未返回时会发生什么的规范。
  • 添加一个用于配置更改的“推送”通知系统。
  • 记录每个受监控的主实例的配置都指定了一个主实例名称,该名称由所有 SENTINEL 命令报告。
  • 明确我们处理单个 Sentinel 监控多个主实例。

快速入门#

获取 Sentinel#

当前版本的 Sentinel 称为 **Sentinel 2**。它是对初始 Sentinel 实现的重写,使用了更强大、更易于预测的算法(本文档中对此进行了说明)。

新开发在 *unstable* 分支中进行,新功能一旦被认为是稳定的,有时会反向移植到最新的稳定分支中。

运行 Sentinel#

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

keydb-sentinel /path/to/sentinel.conf

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

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

两种方式效果相同。

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

Sentinel 默认在 **TCP 端口 26379 上监听连接**,因此为了使 Sentinel 正常工作,您服务器的 26379 端口**必须打开**以接收来自其他 Sentinel 实例 IP 地址的连接。否则,Sentinel 无法通信和达成一致,因此永远不会执行故障转移。

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

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

配置 Sentinel#

KeyDB 源代码分发包含一个名为 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 时,配置也会被重新写入。

上述示例配置主要监控两组 KeyDB 实例,每组包含一个主实例和未定义数量的副本。一组实例称为 mymaster,另一组称为 resque

sentinel monitor 语句的参数含义如下:

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

为了清晰起见,我们逐行查看配置选项的含义:

第一行用于告诉 KeyDB 监控一个名为 *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 来确保一次只有一个副本无法访问。

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

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

  • 主实例特定的配置参数使用 SENTINEL SET 修改。
  • 全局配置参数使用 SENTINEL CONFIG SET 修改。

有关更多信息,请参阅运行时重新配置 Sentinel 部分

Sentinel 部署示例#

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

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

+--------------------+
| 这是一台计算机 |
| 或虚拟机,会独立 |
| 发生故障。我们 |
| 称它为“盒子” |
+--------------------+

我们在方框内写明它们正在运行什么

+-------------------+
| KeyDB 主实例 M1 |
| KeyDB 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 总是需要**与大多数 Sentinel 通信**才能开始故障转移。

示例 1:只有两个 Sentinel,请勿这样做#

+----+ +----+
| M1 |---------| R1 |
| S1 | | S2 |
+----+ +----+
配置:仲裁 = 1
  • 在此设置中,如果主实例 M1 发生故障,R1 将被提升,因为两个 Sentinel 可以就故障达成一致(仲裁显然设置为 1),并且还可以授权故障转移,因为多数为两个。因此,它表面上看起来可能有效,但请检查以下几点以了解为什么此设置已损坏。
  • 如果 M1 所在的盒子停止工作,S1 也会停止工作。在另一个盒子 S2 中运行的 Sentinel 将无法授权故障转移,因此系统将变得不可用。

请注意,为了订购不同的故障转移,并随后将最新配置传播给所有 Sentinel,需要多数。另请注意,在上述设置中,在没有任何协议的情况下,在一侧进行故障转移的能力将非常危险:

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

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

因此,请始终**至少在三个不同的盒子中部署三个 Sentinel**。

示例 2:三个盒子的基本设置#

这是一个非常简单的设置,其优点是易于调整以提高安全性。它基于三个盒子,每个盒子都运行一个 KeyDB 进程和一个 Sentinel 进程。

+----+
| M1 |
| S1 |
+----+
|
+----+ | +----+
| R2 |----+----| R3 |
| S2 | | S3 |
+----+ +----+
配置:仲裁 = 2

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

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

+----+
| M1 |
| S1 | <- C1 (写入将丢失)
+----+
|
/
/
+------+ | +----+
| [M2] |----+----| R3 |
| S2 | | S3 |
+------+ +----+

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

可以使用以下 KeyDB 复制功能来缓解此问题,该功能允许主实例在检测到无法将其写入传输到指定数量的副本时停止接受写入:

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

在上述配置下(更多信息请参阅 KeyDB 分发版中自注释的 keydb.conf 示例),当 KeyDB 实例作为主实例运行时,如果它无法写入至少 1 个副本,它将停止接受写入。由于复制是异步的,**无法写入**实际上意味着副本已断开连接,或者在超过指定的 max-lag 秒数后未向我们发送异步确认。

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

然而,天下没有免费的午餐。有了这种改进,如果两个副本都宕机,主实例将停止接受写入。这是一个权衡。

示例 3:客户端盒子中的 Sentinel#

有时我们只有两个 KeyDB 盒子可用,一个用于主实例,一个用于副本。示例 2 中的配置在这种情况下不可行,因此我们可以采用以下方式,将 Sentinel 放置在客户端所在的位置:

+----+ +----+
| M1 |----+----| R1 |
| | | | |
+----+ | +----+
|
+------------+------------+
| | |
| | |
+----+ +----+ +----+
| C1 | | C2 | | C3 |
| S1 | | S2 | | S3 |
+----+ +----+ +----+
配置:仲裁 = 2

在此设置中,Sentinel 的观点与客户端相同:如果大多数客户端可以访问主实例,则一切正常。C1、C2、C3 这里是通用客户端,不表示 C1 标识连接到 KeyDB 的单个客户端。它更可能是一个应用程序服务器,一个 Rails 应用程序,或类似的东西。

如果 M1 和 S1 所在的盒子发生故障,故障转移将无问题地发生,但很容易看出不同的网络分区将导致不同的行为。例如,如果客户端和 KeyDB 服务器之间的网络断开,Sentinel 将无法设置,因为 KeyDB 主实例和从实例都将不可用。

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

因此,这是一个有效的设置,但示例 2 中的设置具有优势,例如 KeyDB 的 HA 系统与 KeyDB 本身在同一个盒子中运行,这可能更容易管理,并且能够限制少数分区中的主实例可以接收写入的时间。

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

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

+----+ +----+
| M1 |----+----| R1 |
| S1 | | | S2 |
+----+ | +----+
|
+------+-----+
| |
| |
+----+ +----+
| C1 | | C2 |
| S3 | | S4 |
+----+ +----+
配置:仲裁 = 3

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

理论上,此设置通过移除 C2 和 S4 所在的盒子并将仲裁设置为 2 来实现。然而,我们不太可能在 KeyDB 侧需要高可用性而不在应用程序层具有高可用性。

Sentinel、Docker、NAT 和可能的问题#

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

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

端口和地址重映射会以两种方式对 Sentinel 造成问题:

  1. Sentinel 对其他 Sentinel 的自动发现不再有效,因为它基于“hello”消息,其中每个 Sentinel 都会公布它们监听连接的端口和 IP 地址。然而,Sentinel 无法理解地址或端口是否已重映射,因此它正在公布对于其他 Sentinel 连接来说不正确的信息。
  2. 副本在 KeyDB 主实例的 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. 您的 KeyDB 和 Sentinel 节点的名称解析配置必须可靠,并且能够快速解析地址。地址解析中的意外延迟可能会对 Sentinel 产生负面影响。
  2. 您应该在所有地方都使用主机名,避免混用主机名和 IP 地址。为此,请为所有 KeyDB 和 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 端口上运行着一个 KeyDB 主实例,并在 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 事件,您可以通过 Pub/Sub 接收此类事件,如果 Pub/Sub 消息部分中指定那样 SUBSCRIBE 到事件名称。

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

向 Sentinel 查询主实例状态#

使用 Sentinel 开始工作的最明显方法是检查它正在监控的主实例是否正常运行:

$ keydb-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 部署已准备好进行测试。我们可以直接关闭主实例并检查配置是否更改。为此,我们可以这样做:

keydb-cli -p 6379 DEBUG sleep 30

此命令将使我们的主实例在休眠 30 秒后不再可达。它基本上模拟了主实例因某种原因挂起。

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

  1. 每个 Sentinel 都通过 +sdown 事件检测到主实例已宕机。
  2. 此事件随后升级为 +odown,这意味着多个 Sentinel 就主实例不可达达成一致。
  3. Sentinel 投票选举一个 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 是正常的 KeyDB 端口)。Sentinel 使用 KeyDB 协议接受命令,因此您可以使用 keydb-cli 或任何其他未修改的 KeyDB 客户端与 Sentinel 通信。

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

Sentinel 命令#

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

  • SENTINEL CONFIG GET <name> (>= 6.2) 获取全局 Sentinel 配置参数的当前值。指定的名称可以是通配符,类似于 KeyDB 的 CONFIG GET 命令。
  • SENTINEL CONFIG SET <name> <value> (>= 6.2) 设置全局 Sentinel 配置参数的值。
  • SENTINEL CKQUORUM <master name> 检查当前 Sentinel 配置是否能够达到故障转移主实例所需的仲裁,以及授权故障转移所需的多数。此命令应在监控系统中使用,以检查 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 返回主实例和副本的缓存 INFO 输出。
  • SENTINEL IS-MASTER-DOWN-BY-ADDR <ip> <port> <current-epoch> <runid> 检查由 ip:port 指定的主实例从当前 Sentinel 的角度来看是否宕机。此命令主要用于内部使用。
  • 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) 此命令模拟不同的 Sentinel 崩溃场景。
  • SENTINEL RESET <pattern> 此命令将重置所有匹配名称的主实例。pattern 参数是一个 glob 样式模式。重置过程会清除主实例中的任何先前状态(包括正在进行的故障转移),并删除已发现并与主实例关联的所有副本和 Sentinel。

出于连接管理和管理目的,Sentinel 支持 KeyDB 命令的以下子集:

  • 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#

Sentinel 提供了一个 API,用于添加、删除或更改给定主实例的配置。请注意,如果您有多个 Sentinel,则应将更改应用于所有实例,以使 KeyDB 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 命令与 KeyDB 的 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 提供所有配置参数,格式简单易解析(作为字段/值对数组)。

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

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

可操作的全局参数包括

添加或移除 Sentinel#

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

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

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

在此过程结束时,可以使用命令 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 输出中列为正确复制的副本。

发布/订阅消息#

客户端可以使用 Sentinel 作为 KeyDB 兼容的发布/订阅服务器(但您不能使用 PUBLISH),以便 SUBSCRIBEPSUBSCRIBE 到通道并获得特定事件的通知。

通道名称与事件名称相同。例如,名为 +sdown 的通道将接收与进入 SDOWN(SDOWN 表示从您正在查询的 Sentinel 的角度来看实例不再可访问)条件的实例相关的所有通知。

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

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

注意:当指定实例详细信息时,表示提供以下参数以标识目标实例

<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:port 对的从服务器(副本),但同步过程尚未完成。
  • +slave-reconf-done <instance details> -- 从服务器(副本)现在已与新主服务器同步。
  • -dup-sentinel <instance details> -- 指定主服务器的一个或多个 Sentinel 已被移除为重复项(例如,当 Sentinel 实例重新启动时会发生这种情况)。
  • +sentinel <instance details> -- 检测到并连接了此主服务器的新 Sentinel。
  • +sdown <instance details> -- 指定的实例现在处于主观下线状态。
  • -sdown <instance details> -- 指定的实例不再处于主观下线状态。
  • +odown <instance details> -- 指定的实例现在处于客观下线状态。
  • -odown <instance details> -- 指定的实例不再处于客观下线状态。
  • +new-epoch <instance details> -- 当前纪元已更新。
  • +try-failover <instance details> -- 新故障转移正在进行中,等待多数选举。
  • +elected-leader <instance details> -- 赢得指定纪元的选举,可以进行故障转移。
  • +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 -- 退出倾斜模式。

处理 -BUSY 状态#

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

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

副本优先级#

KeyDB 实例有一个名为 replica-priority 的配置参数。此信息由 KeyDB 副本实例在其 INFO 输出中公开,Sentinel 使用它来在可用于故障转移主服务器的副本中选择一个副本

  1. 如果副本优先级设置为 0,则副本永远不会提升为主服务器。
  2. Sentinel 更喜欢优先级数字较低的副本。

例如,如果当前主服务器的数据中心中有一个副本 R1,而另一个副本 R2 在另一个数据中心中,可以将 R1 的优先级设置为 10,将 R2 的优先级设置为 100,这样如果主服务器故障且 R1 和 R2 都可用,则将优先选择 R1。

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

Sentinel 和 KeyDB 身份验证#

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

KeyDB 访问控制列表身份验证#

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

为了使 Sentinel 在配置了 ACL 时连接到 KeyDB 服务器实例,Sentinel 配置必须包含以下指令

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

其中 <username><password> 是用于访问组实例的用户名和密码。这些凭据应在组的所有 KeyDB 实例上配置,并具有最低控制权限。例如

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

KeyDB 仅密码身份验证#

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

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

当使用 Sentinel 时,没有单个主服务器,因为在故障转移后,副本可能会扮演主服务器的角色,并且旧主服务器可以重新配置以充当副本,因此您需要做的是在所有实例(包括主服务器和副本)中设置上述指令。

这通常也是一个合理的设置,因为您不仅希望保护主服务器中的数据,还希望在副本中也能访问相同的数据。

但是,在不需要身份验证即可访问副本的罕见情况下,您仍然可以通过设置副本优先级为零来实现,以防止此副本被提升为主服务器,并且在此副本中仅配置 masterauth 指令,而不使用 requirepass 指令,这样未经身份验证的客户端将能够读取数据。

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

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

配置带身份验证的 Sentinel 实例#

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

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

Sentinel 访问控制列表身份验证#

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

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 客户端的文档。

Sentinel 仅密码身份验证#

要使用仅密码身份验证的 Sentinel,请将 requirepass 配置指令添加到所有 Sentinel 实例,如下所示

requirepass "your_password_here"

以这种方式配置后,Sentinel 将执行两项操作

  1. 客户端需要密码才能向 Sentinel 发送命令。这是显而易见的,因为此配置指令通常在 KeyDB 中就是这样工作的。
  2. 此外,此 Sentinel 实例将使用配置为访问本地 Sentinel 的相同密码来对其连接到的所有其他 Sentinel 实例进行身份验证。

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

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

Sentinel 客户端实现#

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

更高级的概念#

在以下章节中,我们将介绍 Sentinel 工作方式的一些细节,但不涉及本文档最后部分将涵盖的实现细节和算法。

SDOWN 和 ODOWN 故障状态#

KeyDB 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 认为 KeyDB 实例不可用。要触发故障转移,必须达到 ODOWN 状态。

从 SDOWN 切换到 ODOWN 不使用强共识算法,而只是一种流言传播形式:如果给定 Sentinel 收到足够多的 Sentinel 在给定时间范围内报告主服务器不工作,则 SDOWN 将提升为 ODOWN。如果此确认稍后丢失,则清除标志。

要真正启动故障转移,需要使用实际多数的更严格授权,但没有达到 ODOWN 状态就无法触发故障转移。

ODOWN 条件仅适用于主服务器。对于其他类型的实例,Sentinel 不需要采取行动,因此副本和其他 Sentinel 从未达到 ODOWN 状态,而只达到 SDOWN 状态。

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

Sentinel 和副本自动发现#

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

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

同样,您无需配置连接到主服务器的副本列表,因为 Sentinel 将查询 KeyDB 自动发现此列表。

  • 每个 Sentinel 每两秒向每个被监控的主服务器和副本的发布/订阅通道 __sentinel__:hello 发布一条消息,宣布其存在,并带上 ip、port、runid。
  • 每个 Sentinel 都订阅了每个主服务器和副本的发布/订阅通道 __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. 运行 ID。

如果副本与主服务器断开连接的时间超过配置的主服务器超时时间(down-after-milliseconds 选项)的十倍,加上主服务器从执行故障转移的 Sentinel 的角度来看也不可用的时间,则认为该副本不适合进行故障转移并被跳过。

更严格地说,一个副本,其 INFO 输出表明它已与主服务器断开连接超过

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

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

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

  1. 副本按 KeyDB 实例的 keydb.conf 文件中配置的 replica-priority 排序。较低的优先级将被优先选择。
  2. 如果优先级相同,则检查副本处理的复制偏移量,并选择从主服务器接收更多数据的副本。
  3. 如果多个副本具有相同的优先级并从主服务器处理了相同的数据,则会执行进一步检查,选择具有字典序较小运行 ID 的副本。较低的运行 ID 对副本来说并不是真正的优势,但有助于使副本选择过程更具确定性,而不是选择随机副本。

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

KeyDB 实例可以配置特殊的 replica-priority 为零,以便 Sentinel 永远不会将其选为新主服务器。然而,以这种方式配置的副本仍将在故障转移后由 Sentinel 重新配置,以与新主服务器复制,唯一的区别是它永远不会成为主服务器本身。

算法和内部结构#

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

法定人数#

前面的章节表明,每个受 Sentinel 监控的主服务器都与一个配置的法定人数相关联。它指定了需要多少个 Sentinel 进程就主服务器的不可达性或错误情况达成一致,才能触发故障转移。

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

让我们尝试将事情阐述得更清楚一些

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

这个区别可能看起来很微妙,但实际上非常简单易懂和使用。例如,如果您有 5 个 Sentinel 实例,并且法定人数设置为 2,那么一旦有 2 个 Sentinel 认为主服务器无法访问,就会触发故障转移,但是,这两个 Sentinel 中的一个只有在获得至少 3 个 Sentinel 的授权后才能进行故障转移。

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

这意味着法定人数可以用于以两种方式调整 Sentinel

  1. 如果法定人数设置为小于我们部署的 Sentinel 多数的值,我们基本上使 Sentinel 对主服务器故障更加敏感,只要即使是少数 Sentinel 无法再与主服务器通信,就会触发故障转移。
  2. 如果法定人数设置为大于 Sentinel 多数的值,我们使 Sentinel 只有在有大量(大于多数)连接良好的 Sentinel 同意主服务器下线时才能进行故障转移。

配置纪元#

Sentinel 需要获得多数授权才能开始故障转移,原因有以下几点

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

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

KeyDB Sentinel 保证了活跃性属性,即如果大多数 Sentinel 能够通信,那么最终如果主服务器下线,其中一个 Sentinel 将被授权进行故障转移。

KeyDB Sentinel 还保证了安全性属性,即每个 Sentinel 都将使用不同的配置纪元对同一主服务器进行故障转移。

配置传播#

一旦 Sentinel 成功地对主服务器进行故障转移,它将开始广播新配置,以便其他 Sentinel 更新其关于给定主服务器的信息。

要使故障转移被认为是成功的,它需要 Sentinel 能够向选定的副本发送 REPLICAOF NO ONE 命令,并且随后在主服务器的 INFO 输出中观察到切换到主服务器。

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

新配置传播的方式是我们要求每个 Sentinel 故障转移都使用不同版本号(配置纪元)授权的原因。

每个 Sentinel 持续地通过 KeyDB 发布/订阅消息在其主服务器和所有副本中广播其版本的配置。同时,所有 Sentinel 等待消息以查看其他 Sentinel 宣传的配置。

配置在 __sentinel__:hello 发布/订阅通道中广播。

因为每个配置都有不同的版本号,所以较大版本总是优于较小版本。

例如,主服务器 mymaster 的配置开始时,所有 Sentinel 都认为主服务器位于 192.168.1.50:6379。此配置的版本为 1。一段时间后,一个 Sentinel 被授权以版本 2 进行故障转移。如果故障转移成功,它将开始广播新配置,假设为 192.168.1.50:9000,版本为 2。所有其他实例将看到此配置并相应地更新其配置,因为新配置具有更大的版本。

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

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

分区下的一致性#

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

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

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

以下是一个简单的网络,其中有 3 个节点,每个节点运行一个 KeyDB 实例和一个 Sentinel 实例

+-------------+
| Sentinel 1 |----- 客户端 A
| KeyDB 1 (M) |
+-------------+
|
|
+-------------+ | +------------+
| Sentinel 2 |-----+-- // ----| Sentinel 3 |----- 客户端 B
| KeyDB 2 (S) | | KeyDB 3 (M)|
+-------------+ +------------+

在此系统中,原始状态是 KeyDB 3 是主服务器,而 KeyDB 1 和 2 是副本。发生了一个分区,隔离了旧的主服务器。Sentinel 1 和 2 启动了故障转移,将 Sentinel 1 提升为新的主服务器。

Sentinel 属性保证 Sentinel 1 和 2 现在拥有主服务器的新配置。然而,Sentinel 3 仍然拥有旧配置,因为它位于不同的分区中。

我们知道当网络分区愈合时,Sentinel 3 的配置将得到更新,但是如果在分区期间有客户端与旧主服务器分区,会发生什么?

客户端仍将能够写入 KeyDB 3,即旧主服务器。当分区重新连接时,KeyDB 3 将变为 KeyDB 1 的副本,并且在分区期间写入的所有数据都将丢失。

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

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

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

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

在上述配置下(更多信息请参阅 KeyDB 分发版中自注释的 keydb.conf 示例),当 KeyDB 实例作为主实例运行时,如果它无法写入至少 1 个副本,它将停止接受写入。由于复制是异步的,**无法写入**实际上意味着副本已断开连接,或者在超过指定的 max-lag 秒数后未向我们发送异步确认。

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

通常,KeyDB + Sentinel 整体是一个最终一致的系统,其合并函数是最后一次故障转移获胜,并且旧主服务器的数据被丢弃以复制当前主服务器的数据,因此总存在丢失已确认写入的窗口。这是由于 KeyDB 异步复制和系统“虚拟”合并函数的丢弃性质造成的。请注意,这不是 Sentinel 本身的限制,如果您通过强一致性复制状态机协调故障转移,相同的属性仍然适用。避免丢失已确认写入的方法只有两种

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

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

Sentinel 持久状态#

Sentinel 状态持久化在 sentinel 配置文件中。例如,每次接收或创建(领导 Sentinel)新的主服务器配置时,配置都会与配置纪元一起持久化到磁盘。这意味着停止和重新启动 Sentinel 进程是安全的。

TILT 模式#

KeyDB 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 命令,可能会得到以下响应

$ keydb-cli -p 26379
127.0.0.1:26379> info
(省略了 Sentinel 服务器的其他信息。)
# 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 来替换。但是,目前尚不清楚这是否是一个好的解决方案,因为当前系统避免了进程只是暂停或长时间未被调度程序执行的问题。

警告: 本文档为草稿,其中包含的指南可能会随着 Sentinel 项目的发展而改变。

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