Unity3D热门教程

游戏开发工具

Redis Sentinel 的高可用性(三)

#哨兵和副本自动发现

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

此功能是通过将hello 消息发送到名为 的通道 来实现的__sentinel__:hello

同样,您不需要配置附加到主服务器的副本列表,因为 Sentinel 会自动发现此列表以查询 Redis。

  • __sentinel__:hello每个 Sentinel每隔两秒向每个受监控的主和副本 Pub/Sub 通道发布一条消息,通过 ip、端口、runid 宣布它的存在。

  • 每个 Sentinel 都订阅了__sentinel__:hello每个 master 和 replica 的 Pub/Sub 频道,寻找未知的 sentinel。当检测到新的哨兵时,它们将被添加为此主服务器的哨兵。

  • Hello 消息还包括主设备的完整当前配置。如果接收 Sentinel 的给定 master 的配置比收到的要旧,它会立即更新到新配置。

  • 在将新哨兵添加到主服务器之前,哨兵始终检查是否已经存在具有相同 runid 或相同地址(ip 和端口对)的哨兵。在这种情况下,所有匹配的哨兵都将被删除,并添加新的哨兵。

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

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

  • 声称是主服务器的副本(根据当前配置)将被配置为副本以与当前主服务器进行复制。

  • 连接到错误主服务器的副本将被重新配置为使用正确的主服务器进行复制。

Sentinel 要重新配置副本,必须在一段时间内观察错误配置,这比用于广播新配置的时间长。

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

还要注意总是试图强加当前配置的语义如何使故障转移更能抵抗分区:

  • 故障转移的主节点在返回可用时被重新配置为副本。

  • 在分区期间被分区的副本一旦到达就会重新配置。

关于本节要记住的重要一课是:Sentinel 是一个系统,其中每个进程将始终尝试将最后的逻辑配置强加到受监控的实例集

#副本选择和优先级

当 Sentinel 实例准备好执行故障转移时,由于 master 处于ODOWN状态并且 Sentinel 从大多数已知的 Sentinel 实例接收到故障转移授权,因此需要选择合适的副本。

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

  1. 与主站断开连接的时间。

  2. 副本优先级。

  3. 已处理复制偏移量。

  4. 运行标识。

发现副本与主服务器断开连接超过配置的主服务器超时时间(毫秒后关闭选项)的十倍以上,加上从 Sentinel 进行故障转移的角度来看主服务器也不可用的时间,被认为不适合故障转移并被跳过。

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

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

被认为是不可靠的,完全不予理会。

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

  1. 副本按照Redis 实例文件中的replica-priority配置进行排序。redis.conf较低的优先级将是首选。

  2. 如果优先级相同,则检查replica处理的replication offset,选择从master接收到更多数据的replica。

  3. 如果多个副本具有相同的优先级并处理来自主节点的相同数据,则执行进一步检查,选择具有字典顺序较小的运行 ID 的副本。具有较低的运行 ID 对副本来说并不是真正的优势,但它有助于使副本选择过程更具确定性,而不是诉诸于选择随机副本。

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

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

#算法和内部结构

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

#法定人数

前面的部分展示了 Sentinel 监控的每个主节点都与配置的仲裁相关联。它指定需要就主服务器的不可达性或错误条件达成一致以触发故障转移的 Sentinel 进程的数量。

但是,在触发故障转移之后,为了真正执行故障转移,至少需要大多数 Sentinel 授权 Sentinel 进行故障转移。Sentinel 永远不会在存在少数 Sentinel 的分区中执行故障转移。

让我们试着让事情更清楚一点:

  • Quorum:需要检测错误情况以便将主节点标记为ODOWN的 Sentinel 进程的数量。

  • 故障转移由ODOWN状态触发。

  • 一旦触发故障转移,尝试故障转移的 Sentinel 需要向大多数 Sentinel 请求授权(如果仲裁设置为大于多数的数字,则需要超过多数)。

差异可能看起来很微妙,但实际上很容易理解和使用。例如,如果您有 5 个 Sentinel 实例,并且仲裁设置为 2,则一旦 2 个 Sentinel 认为主服务器不可访问,就会触发故障转移,但是两个 Sentinel 中的一个只有在它获得时才能进行故障转移至少来自 3 个 Sentinel 的授权。

相反,如果将 quorum 配置为 5,则所有 Sentinel 必须就主错误条件达成一致,并且需要所有 Sentinel 的授权才能进行故障转移。

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

  1. 如果 quorum 设置为小于我们部署的大多数 Sentinel 的值,我们基本上使 Sentinel 对 master 故障更加敏感,即使只有少数 Sentinel 不再能够与 master 通信,就会触发故障转移。

  2. 如果将仲裁设置为大于大多数 Sentinel 的值,则我们使 Sentinel 仅在有大量(大于大多数)连接良好的 Sentinel 同意主节点关闭时才能进行故障转移。

#配置时期

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

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

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

Redis Sentinel 保证了活性属性,即如果大多数 Sentinel 能够交谈,最终如果主服务器宕机,一个将被授权进行故障转移。

Redis Sentinel 还保证了每个 Sentinel 将使用不同的配置 epoch故障转移同一个 master 的**安全属性。

#配置传播

一旦 Sentinel 能够成功地对 master 进行故障转移,它将开始广播新配置,以便其他 Sentinel 将更新其有关给定 master 的信息。

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

此时,即使正在进行副本的重新配置,也认为故障转移成功,需要所有的 Sentinel 开始上报新的配置。

传播新配置的方式是我们需要为每个 Sentinel 故障转移授权不同版本号(配置时期)的原因。

每个 Sentinel 都使用 Redis Pub/Sub 消息在主服务器和所有副本中持续广播其主服务器配置版本。同时,所有的 Sentinel 都在等待消息,以查看其他 Sentinel 发布的配置是什么。

配置在__sentinel__:helloPub/Sub 频道中广播。

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

因此,例如,master 的配置mymaster从所有相信 master 的 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 是副本。发生了隔离旧主服务器的分区。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 整体是一个最终一致的系统,merge 功能是last failover wins,并丢弃旧 master 的数据以复制当前 master 的数据,因此始终存在丢失确认写入的窗口。这是由于 Redis 异步复制和系统的“虚拟”合并功能的丢弃性质。请注意,这不是 Sentinel 本身的限制,如果您使用高度一致的复制状态机编排故障转移,相同的属性仍将适用。只有两种方法可以避免丢失已确认的写入:

  1. 使用同步复制(以及适当的共识算法来运行复制状态机)。

  2. 使用最终一致的系统,可以合并同一对象的不同版本。

Redis 目前无法使用上述任何系统,并且目前超出了开发目标。 然而,在 Redis 商店(例如 SoundCloud Roshi 或 Netflix Dynomite )之上,有一些代理实现了解决方案“2” 。

#哨兵持久状态

哨兵状态保存在哨兵配置文件中。例如,每当一个新的配置被接收或创建(leader Sentinels),对于一个master,配置与配置epoch一起被持久化在磁盘上。这意味着停止和重新启动 Sentinel 进程是安全的。

#倾斜模式

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。

请注意,在某些方面,可以使用许多内核提供的单调时钟 API 来替换 TILT 模式。但是,目前尚不清楚这是否是一个好的解决方案,因为当前系统避免了进程刚刚挂起或调度程序长时间不执行的问题。

关于使用的单词 slave 的说明:从 Redis 5 开始,如果不是为了向后兼容,Redis 项目不再使用单词 slave。不幸的是,在这个命令中,slave 这个词是协议的一部分,所以只有当这个 API 被自然弃用时,我们才能删除此类事件。