深入了解集群教程
本文档是对 KeyDB 集群的简要介绍,不涉及难以理解的分布式系统概念。它提供了有关如何设置、测试和操作集群的说明,但不会深入探讨KeyDB 集群规范中涵盖的细节,而是从用户的角度描述系统的行为。
然而,本教程试图以一种易于理解的方式,从最终用户的角度提供有关 KeyDB 集群的可用性和一致性特征的信息。
如果您计划运行一个重要的 KeyDB 集群部署,建议阅读更正式的规范,尽管并非强制要求。然而,一个好主意是从本文档开始,花一些时间玩转 KeyDB 集群,然后再阅读规范。
#
KeyDB 集群入门KeyDB 集群提供了一种运行 KeyDB 安装的方式,其中数据**在多个 KeyDB 节点之间自动分片**。
KeyDB 集群还在**分区期间提供一定程度的可用性**,实际上,这意味着在某些节点发生故障或无法通信时仍能继续操作。然而,在发生较大规模的故障时(例如,当大多数主节点不可用时),集群将停止运行。
那么,在实际应用中,您能从 KeyDB 集群中得到什么?
- 能够**在多个节点之间自动分割您的数据集**。
- 能够在**部分节点出现故障或无法与集群其余部分通信时继续操作**。
#
KeyDB 集群 TCP 端口每个 KeyDB 集群节点都需要打开两个 TCP 连接。一个是用于为客户端提供服务的普通 KeyDB TCP 端口(例如 6379),另一个是在数据端口上加 10000 得到的端口(例如 16379)。
这个第二个*高*端口用于集群总线,这是一个使用二进制协议的节点间通信通道。集群总线被节点用于故障检测、配置更新、故障转移授权等。客户端绝不应尝试与集群总线端口通信,而应始终与普通的 KeyDB 命令端口通信。但是,请确保在防火墙中打开这两个端口,否则 KeyDB 集群节点将无法通信。
命令端口和集群总线端口的偏移量是固定的,始终为 10000。
请注意,为了让 KeyDB 集群正常工作,您需要为每个节点:
- 用于与客户端通信的普通客户端通信端口(通常为 6379)需要对所有需要访问集群的客户端以及所有其他集群节点(它们使用客户端端口进行键迁移)开放。
- 集群总线端口(客户端端口 + 10000)必须可以从所有其他集群节点访问。
如果您不打开这两个 TCP 端口,您的集群将无法按预期工作。
集群总线使用一种不同的二进制协议进行节点间数据交换,这种协议更适合使用较少的带宽和处理时间在节点之间交换信息。
#
KeyDB 集群与 Docker目前,KeyDB 集群不支持 NAT 环境以及通常情况下 IP 地址或 TCP 端口被重映射的环境。
Docker 使用一种称为*端口映射*的技术:在 Docker 容器内运行的程序可能会暴露一个与程序本身认为正在使用的端口不同的端口。这对于在同一台服务器上同时运行多个使用相同端口的容器非常有用。
为了使 Docker 与 KeyDB 集群兼容,您需要使用 Docker 的**主机网络模式**。有关更多信息,请查看 Docker 文档中的 `--net=host` 选项。
#
KeyDB 集群数据分片KeyDB 集群不使用一致性哈希,而是使用一种不同的分片形式,其中每个键在概念上都属于我们称之为**哈希槽**的一部分。
KeyDB 集群中有 16384 个哈希槽,要计算给定键的哈希槽,我们只需取该键的 CRC16 值对 16384 取模。
KeyDB 集群中的每个节点负责一部分哈希槽,例如,您可能有一个包含 3 个节点的集群,其中:
- 节点 A 包含从 0 到 5500 的哈希槽。
- 节点 B 包含从 5501 到 11000 的哈希槽。
- 节点 C 包含从 11001 到 16383 的哈希槽。
这样可以轻松地在集群中添加和移除节点。例如,如果我想添加一个新节点 D,我需要将一些哈希槽从节点 A、B、C 移动到 D。同样,如果我想从集群中移除节点 A,我只需将 A 服务的哈希槽移动到 B 和 C。当节点 A 变空后,我就可以完全从集群中移除它。
因为将哈希槽从一个节点移动到另一个节点不需要停止操作,所以添加和移除节点,或者更改节点持有的哈希槽百分比,都不需要任何停机时间。
KeyDB 集群支持多键操作,只要单个命令执行(或整个事务、或 Lua 脚本执行)中涉及的所有键都属于同一个哈希槽。用户可以使用一种称为*哈希标签*的概念来强制多个键属于同一个哈希槽。
哈希标签在 KeyDB 集群规范中有详细说明,但要点是,如果一个键中有一个用 {} 括号括起来的子字符串,那么只有括号内的字符串会被哈希,例如 `this{foo}key` 和 `another{foo}key` 保证在同一个哈希槽中,并且可以在一个多键命令中一起作为参数使用。
#
KeyDB 集群主从模型为了在部分主节点发生故障或无法与大多数节点通信时保持可用,KeyDB 集群使用主从模型,其中每个哈希槽有 1(主节点本身)到 N 个副本(N-1 个额外的副本节点)。
在我们包含节点 A、B、C 的示例集群中,如果节点 B 发生故障,集群将无法继续运行,因为我们再也无法为 5501-11000 范围内的哈希槽提供服务。
然而,当集群创建时(或稍后),我们为每个主节点添加一个副本节点,这样最终的集群就由主节点 A、B、C 和副本节点 A1、B1、C1 组成。这样,如果节点 B 发生故障,系统仍然能够继续运行。
节点 B1 复制 B,如果 B 发生故障,集群会将节点 B1 提升为新的主节点,并继续正常运行。
然而,请注意,如果节点 B 和 B1 同时发生故障,KeyDB 集群将无法继续运行。
#
KeyDB 集群一致性保证KeyDB 集群无法保证**强一致性**。实际上,这意味着在某些条件下,KeyDB 集群可能会丢失已经被系统向客户端确认的写操作。
KeyDB 集群可能丢失写操作的第一个原因是因为它使用异步复制。这意味着在写操作期间,会发生以下情况:
- 您的客户端写入主节点 B。
- 主节点 B 向您的客户端回复 OK。
- 主节点 B 将写操作传播到其副本 B1、B2 和 B3。
如您所见,B 在回复客户端之前不会等待来自 B1、B2、B3 的确认,因为这对 KeyDB 来说会造成过高的延迟惩罚。因此,如果您的客户端写入了某些内容,B 确认了写操作,但在能够将写操作发送到其副本之前崩溃了,那么其中一个副本(未收到写操作)可能会被提升为主节点,从而永久丢失该写操作。
这与大多数配置为每秒将数据刷新到磁盘的数据库**非常相似**,所以这是您已经能够根据以往不涉及分布式系统的传统数据库系统经验来理解的场景。同样,您可以通过强制数据库在回复客户端之前将数据刷新到磁盘来提高一致性,但这通常会导致性能过低。在 KeyDB 集群的情况下,这相当于同步复制。
基本上,需要在性能和一致性之间进行权衡。
KeyDB 集群在绝对需要时支持通过 `WAIT` 命令实现的同步写入。这使得丢失写入的可能性大大降低。然而,请注意,即使使用同步复制,KeyDB 集群也不实现强一致性:在更复杂的故障场景下,一个未能接收到写入的副本总是有可能被选举为主节点。
还有另一个值得注意的场景,KeyDB 集群会丢失写操作,这发生在网络分区期间,一个客户端与少数实例(包括至少一个主节点)被隔离。
以我们的 6 节点集群为例,该集群由 A、B、C、A1、B1、C1 组成,有 3 个主节点和 3 个副本。还有一个客户端,我们称之为 Z1。
分区发生后,有可能在分区的一侧有 A、C、A1、B1、C1,而在另一侧有 B 和 Z1。
Z1 仍然能够向 B 写入,B 也会接受其写入。如果分区在很短的时间内恢复,集群将正常继续。然而,如果分区持续足够长的时间,以至于 B1 在分区的大多数一侧被提升为主节点,那么 Z1 在此期间发送给 B 的写入将会丢失。
请注意,Z1 能够发送给 B 的写入量有一个**最大窗口**:如果已经过去了足够的时间,让分区的大多数一侧选举出一个副本作为主节点,那么少数一侧的每个主节点都将停止接受写入。
这个时间量是 KeyDB 集群一个非常重要的配置指令,称为**节点超时**。
节点超时时间过后,一个主节点被认为是故障的,并且可以被其副本之一替换。同样,在节点超时时间过后,如果一个主节点无法感知到大多数其他主节点,它将进入错误状态并停止接受写入。
#
KeyDB 集群配置参数我们即将创建一个示例集群部署。在继续之前,让我们介绍一下 KeyDB 集群在 `keydb.conf` 文件中引入的配置参数。有些会很明显,有些则在您继续阅读时会更清楚。
- cluster-enabled `<yes/no>`:如果为 yes,则在特定的 KeyDB 实例中启用 KeyDB 集群支持。否则,该实例将像往常一样作为独立实例启动。
- cluster-config-file `<filename>`:请注意,尽管此选项的名称如此,但它不是用户可编辑的配置文件,而是 KeyDB 集群节点在每次发生更改时自动持久化集群配置(即状态)的文件,以便在启动时能够重新读取。该文件列出了集群中的其他节点、它们的状态、持久变量等。通常,由于某些消息的接收,该文件会被重写并刷新到磁盘。
- cluster-node-timeout `<milliseconds>`:KeyDB 集群节点可以不可用的最长时间,而不会被认为是故障。如果一个主节点在超过指定的时间内无法访问,它将被其副本进行故障转移。该参数控制 KeyDB 集群中的其他重要事项。值得注意的是,任何在指定时间内无法联系到大多数主节点的节点都将停止接受查询。
- cluster-replica-validity-factor `<factor>`:如果设置为零,副本将始终认为自己是有效的,因此将始终尝试对主节点进行故障转移,无论主从之间的连接断开多长时间。如果该值为正,则最大断开时间计算为*节点超时*值乘以该选项提供的因子,如果节点是副本,则在主从连接断开时间超过指定时间时,它不会尝试启动故障转移。例如,如果节点超时设置为 5 秒,有效性因子设置为 10,则与主节点断开连接超过 50 秒的副本将不会尝试对其主节点进行故障转移。请注意,任何非零值都可能导致 KeyDB 集群在主节点故障后不可用,如果没有副本能够进行故障转移。在这种情况下,只有当原始主节点重新加入集群时,集群才会恢复可用。
- cluster-migration-barrier `<count>`:一个主节点将保持连接的最小副本数,以便另一个副本迁移到不再被任何副本覆盖的主节点。有关副本迁移的更多信息,请参阅本教程中的相应部分。
- cluster-require-full-coverage `<yes/no>`:如果此项设置为 yes(默认值),当部分键空间未被任何节点覆盖时,集群将停止接受写入。如果此选项设置为 no,即使只能处理部分键的请求,集群仍将提供查询服务。
- cluster-allow-reads-when-down `<yes/no>`:如果此项设置为 no(默认值),当集群标记为失败时(无论是节点无法达到主节点法定数量还是未满足完全覆盖),KeyDB 集群中的节点将停止处理所有流量。这可以防止从一个不知道集群变化的节点读取可能不一致的数据。此选项可以设置为 yes,以允许在故障状态下从节点读取数据,这对于希望优先考虑读取可用性但仍想防止不一致写入的应用程序非常有用。当使用仅有一两个分片的 KeyDB 集群时,它也很有用,因为它允许节点在主节点故障但无法进行自动故障转移时继续处理写入。
#
创建和使用 KeyDB 集群注意:要手动部署 KeyDB 集群,**学习**其某些操作方面非常重要。然而,如果你想尽快(As Soon As Possible)让集群运行起来,请跳过本节和下一节,直接进入**使用 create-cluster 脚本创建 KeyDB 集群**。
要创建一个集群,我们首先需要有一些以**集群模式**运行的空 KeyDB 实例。这基本上意味着集群不是使用普通的 KeyDB 实例创建的,因为需要配置一种特殊模式,以便 KeyDB 实例能够启用集群特定的功能和命令。
以下是一个最小的 KeyDB 集群配置文件:
正如您所见,启用集群模式的仅仅是 `cluster-enabled` 指令。每个实例还包含一个文件的路径,该文件存储此节点的配置,默认为 `nodes.conf`。这个文件永远不应由人工修改;它由 KeyDB 集群实例在启动时自动生成,并在需要时更新。
请注意,能够正常工作的**最小集群**至少需要包含三个主节点。对于您的首次测试,强烈建议启动一个包含三个主节点和三个副本的六节点集群。
为此,进入一个新目录,并创建以下目录,目录名以我们将在每个目录中运行的实例的端口号命名。
类似这样:
在从 7000 到 7005 的每个目录中创建一个 `keydb.conf` 文件。作为配置文件的模板,只需使用上面的小示例,但请确保根据目录名称将端口号 `7000` 替换为正确的端口号。
现在,将您从 **GitHub 上 unstable 分支的最新源码编译**的 keydb-server 可执行文件复制到 `cluster-test` 目录中,最后在您喜欢的终端应用程序中打开 6 个终端标签页。
像这样启动每个实例,每个标签页一个:
正如您从每个实例的日志中看到的,由于不存在 `nodes.conf` 文件,每个节点都会为自己分配一个新的 ID。
此 ID 将被此特定实例永久使用,以便该实例在集群上下文中具有唯一的名称。每个节点都使用此 ID 来记住其他所有节点,而不是通过 IP 或端口。IP 地址和端口可能会改变,但唯一的节点标识符在节点的整个生命周期中永远不会改变。我们简单地称之为**节点 ID**。
#
创建集群现在我们已经有了一些正在运行的实例,我们需要通过向节点写入一些有意义的配置来创建我们的集群。
如果您使用的是 KeyDB 5 或更高版本,这非常容易实现,因为我们有嵌入在 `keydb-cli` 中的 KeyDB 集群命令行实用程序的帮助,该实用程序可用于创建新集群、检查或重新分片现有集群等等。
要使用 `keydb-cli` 为 KeyDB 5 创建集群,只需键入:
这里使用的命令是 **create**,因为我们想要创建一个新集群。选项 `--cluster-replicas 1` 意味着我们希望为每个创建的主节点配备一个副本。其他参数是我想要用来创建新集群的实例地址列表。
显然,满足我们要求的唯一设置是创建一个包含 3 个主节点和 3 个副本的集群。
keydb-cli 会向你提出一个配置。输入 **yes** 接受建议的配置。集群将被配置和*加入*,这意味着实例将被引导以相互通信。最后,如果一切顺利,你会看到类似这样的消息:
这意味着至少有一个主实例为 16384 个可用槽中的每一个提供服务。
#
使用 create-cluster 脚本创建 KeyDB 集群如果您不想像上面解释的那样通过手动配置和执行单个实例来创建 KeyDB 集群,有一个更简单的系统(但您将学不到同样多的操作细节)。
只需检查 KeyDB 发行版中的 `utils/create-cluster` 目录。里面有一个名为 `create-cluster` 的脚本(与其所在的目录同名),它是一个简单的 bash 脚本。要启动一个包含 3 个主节点和 3 个副本的 6 节点集群,只需键入以下命令:
create-cluster start
create-cluster create
在第 2 步中,当 `keydb-cli` 工具希望您接受集群布局时,回答 `yes`。
您现在可以与集群交互,默认情况下第一个节点将从端口 30001 开始。完成后,用以下命令停止集群:
create-cluster stop
.
请阅读此目录中的 `README` 文件,以获取有关如何运行该脚本的更多信息。
测试 KeyDB 集群的一个简单方法是尝试上述任何一个客户端,或者简单地使用 `keydb-cli` 命令行工具。以下是使用后者的交互示例:
**注意:** 如果您使用脚本创建了集群,您的节点可能会监听不同的端口,默认从 30001 开始。
keydb-cli 的集群支持非常基础,因此它总是利用 KeyDB 集群节点能够将客户端重定向到正确节点这一事实。一个专业的客户端能够做得更好,并且会缓存哈希槽和节点地址之间的映射关系,以便直接使用正确的连接到正确的节点。只有当集群配置发生变化时,例如在故障转移后或系统管理员通过添加或删除节点来更改集群布局后,该映射才会刷新。
#
使用 keydb-rb-cluster 编写示例应用在继续展示如何操作 KeyDB 集群,如进行故障转移或重新分片之前,我们需要创建一些示例应用程序,或者至少能够理解一个简单的 KeyDB 集群客户端交互的语义。
这样,我们就可以运行一个示例,同时尝试让节点失效,或者开始重新分片,以观察 KeyDB 集群在真实世界条件下的行为。在没有人向集群写入时观察会发生什么,帮助不大。
本节通过两个示例解释了redis-rb-cluster的一些基本用法。第一个是以下内容,它是 redis-rb-cluster 发行版中的`example.rb`文件:
应用程序做了一件非常简单的事情,它将形式为 `foo<number>` 的键设置为 `number`,一个接一个。所以如果你运行这个程序,结果是以下命令流:
- SET foo0 0
- SET foo1 1
- SET foo2 2
- 以此类推...
这个程序看起来比它通常应该的要复杂,因为它被设计用来在屏幕上显示错误,而不是因为异常而退出,所以与集群执行的每个操作都被包裹在 `begin` `rescue` 块中。
程序的**第 14 行**是第一个有趣的地方。它创建了 KeyDB 集群对象,使用的参数包括一个*启动节点*列表、该对象允许对不同节点建立的最大连接数,以及最后是一个给定操作被视为失败的超时时间。
启动节点不需要是集群的所有节点。重要的是至少有一个节点是可达的。还要注意,redis-rb-cluster 在能够与第一个节点连接后会立即更新这个启动节点列表。你应该期望任何其他专业的客户端都有类似的行为。
现在我们已经将 KeyDB 集群对象实例存储在 **rc** 变量中,我们准备像使用普通的 KeyDB 对象实例一样使用该对象。
这正是**第 18 至 26 行**所发生的事情:当我们重新启动示例时,我们不想再从 `foo0` 开始,所以我们将计数器存储在 KeyDB 本身。上面的代码旨在读取这个计数器,或者如果计数器不存在,则将其值赋为零。
然而,请注意它是一个 while 循环,因为即使集群宕机并返回错误,我们也想一次又一次地尝试。正常的应用程序不需要这么小心。
**第 28 行到第 37 行之间**启动了主循环,其中键被设置或显示错误。
注意循环末尾的 `sleep` 调用。在您的测试中,如果您想以最快的速度向集群写入(当然,相对于这是一个没有真正并行性的忙碌循环而言,所以在最佳条件下您通常会得到每秒 1 万次操作),可以移除 sleep。
通常写入速度会减慢,以便示例应用程序更易于人类理解。
启动应用程序会产生以下输出:
这不是一个非常有趣的程序,我们稍后会使用一个更好的程序,但我们已经可以看到在程序运行时进行重新分片时会发生什么。
#
对集群进行重新分片现在我们准备尝试集群重新分片。为此,请保持 example.rb 程序运行,这样您就可以看到对正在运行的程序是否有任何影响。另外,您可能希望注释掉 `sleep` 调用,以便在重新分片期间有更严峻的写入负载。
重新分片(Resharding)基本上意味着将哈希槽从一组节点移动到另一组节点,和创建集群一样,它也是使用 keydb-cli 工具完成的。
要开始重新分片,只需输入:
你只需要指定一个节点,keydb-cli 会自动找到其他节点。
目前,keydb-cli 只能在管理员的支持下进行重新分片,你不能只说从这个节点移动 5% 的槽到另一个节点(但这实现起来相当简单)。所以它会从提问开始。第一个问题是你想进行多大规模的重新分片:
我们可以尝试重新分片 1000 个哈希槽,如果示例程序在没有 sleep 调用的情况下仍在运行,这应该已经包含了相当数量的键。
然后,keydb-cli 需要知道重新分片的目标是什么,也就是说,哪个节点将接收这些哈希槽。我将使用第一个主节点,即 127.0.0.1:7000,但我需要指定该实例的节点 ID。这已经由 keydb-cli 在一个列表中打印出来了,但如果需要,我随时可以用以下命令找到一个节点的 ID:
好的,所以我的目标节点是 97a3a64667477371c4479320d683e4c8db5858b1。
现在你会看到一个问题,你希望从哪些节点获取这些键。我将直接输入 `all`,以便从所有其他主节点获取一些哈希槽。
在最终确认后,您会看到一条消息,内容是 keydb-cli 将要从一个节点移动到另一个节点的每个槽,并且每当一个实际的键从一侧移动到另一侧时,都会打印一个点。
在重新分片进行期间,您应该能看到您的示例程序不受影响地运行。如果您愿意,可以在重新分片期间多次停止和重新启动它。
在重新分片结束时,您可以使用以下命令测试集群的健康状况:
所有的槽位都会像往常一样被覆盖,但这次位于 127.0.0.1:7000 的主节点将拥有更多的哈希槽,大约在 6461 个左右。
#
编写重新分片操作脚本重新分片可以自动执行,无需以交互方式手动输入参数。这可以通过类似以下的命令行实现:
如果您可能经常进行重新分片,这允许构建一些自动化操作。然而,目前 `keydb-cli` 还没有办法通过检查集群节点间的键分布并智能地移动槽位来自动重新平衡集群。这个功能将在未来添加。
`--cluster-yes` 选项指示集群管理器自动对命令的提示回答“yes”,使其能够以非交互模式运行。请注意,也可以通过设置 `REDISCLI_CLUSTER_YES` 环境变量来激活此选项。
#
一个更有趣的示例应用程序我们早期编写的示例应用程序不是很好。它以一种简单的方式向集群写入数据,甚至不检查写入的内容是否正确。
从我们的角度来看,接收写入的集群可能只是在每次操作时都将键 `foo` 写入 `42`,而我们根本不会注意到。
因此,在 `redis-rb-cluster` 仓库中,有一个更有趣的应用程序,名为 `consistency-test.rb`。它使用一组计数器,默认为 1000 个,并发送 `INCR` 命令来增加这些计数器。
然而,应用程序不仅仅是写入,它还做了另外两件事:
- 当使用 `INCR` 更新计数器时,应用程序会记住这次写入。
- 在每次写入之前,它还会读取一个随机计数器,并检查其值是否与我们预期的相符,将其与内存中的值进行比较。
这意味着这个应用程序是一个简单的**一致性检查器**,能够告诉您集群是否丢失了某些写入,或者是否接受了我们没有收到确认的写入。在第一种情况下,我们会看到一个计数器的值小于我们记住的值,而在第二种情况下,值会更大。
运行 consistency-test 应用程序每秒产生一行输出:
该行显示了执行的**R**eads(读取)和**W**rites(写入)次数,以及错误数量(由于系统不可用而未被接受的查询)。
如果发现任何不一致,新的行将被添加到输出中。例如,如果我在程序运行时手动重置一个计数器,就会发生这种情况:
当我将计数器设置为 0 时,实际值是 114,所以程序报告丢失了 114 次写入(集群未记住的 `INCR` 命令)。
这个程序作为一个测试案例更有趣,所以我们将用它来测试 KeyDB 集群的故障转移。
#
测试故障转移注意:在这次测试中,您应该打开一个运行着一致性测试应用程序的标签页。
为了触发故障转移,我们能做的最简单的事情(这也是分布式系统中最简单的语义故障)是让单个进程崩溃,在我们的例子中是单个主节点。
我们可以用以下命令识别一个主节点并使其崩溃:
好的,所以 7000、7001 和 7002 是主节点。让我们用 **DEBUG SEGFAULT** 命令使节点 7002 崩溃:
现在我们可以查看一致性测试的输出来看看它报告了什么。
如您所见,在故障转移期间,系统无法接受 578 次读取和 577 次写入,但数据库中没有产生不一致。这可能听起来出乎意料,因为在本教程的第一部分我们说过 KeyDB 集群在故障转移期间可能会因为使用异步复制而丢失写入。我们没有说的是,这种情况不太可能发生,因为 KeyDB 大约在同一时间向客户端发送回复并将命令复制到副本,所以丢失数据的窗口非常小。然而,难以触发并不意味着不可能,所以这并不能改变 KeyDB 集群提供的一致性保证。
我们现在可以检查故障转移后的集群设置(注意,在此期间我重新启动了崩溃的实例,使其作为副本重新加入集群{显示为`slave`}):
现在主节点运行在 7000、7001 和 7005 端口上。之前作为主节点的,也就是在 7002 端口上运行的 KeyDB 实例,现在是 7005 的副本。
`CLUSTER NODES` 命令的输出可能看起来很吓人,但实际上非常简单,由以下标记组成:
- 节点 ID
- ip:port
- 标志:master, slave(表示为 replica), myself, fail, ...
- 如果是副本,则为主节点的节点 ID
- 上一次等待回复的待处理 PING 的时间。
- 上一次接收到 PONG 的时间。
- 此节点的配置纪元(请参阅集群规范)。
- 到此节点的链接状态。
- 服务的槽位...
#
手动故障转移有时,在不给主节点造成任何问题的情况下强制进行故障转移是很有用的。例如,为了升级其中一个主节点的 KeyDB 进程,一个好主意是将其故障转移,使其变为副本,从而对可用性的影响降到最低。
KeyDB 集群支持手动故障转移,使用 `CLUSTER FAILOVER` 命令,该命令必须在您要故障转移的主节点的**副本之一**上执行。
手动故障转移是特殊的,并且比由实际主节点故障引起的故障转移更安全,因为它们以避免在此过程中丢失数据的方式进行,只有当系统确定新主节点已经处理了来自旧主节点的所有复制流时,才会将客户端从原始主节点切换到新主节点。
当您执行手动故障转移时,您会在副本日志中看到以下内容:
基本上,连接到我们正在进行故障转移的主节点的客户端会被停止。同时,主节点将其复制偏移量发送给副本,副本等待在其一侧达到该偏移量。当达到复制偏移量时,故障转移开始,旧主节点被告知配置切换。当旧主节点上的客户端被解锁时,它们会被重定向到新主节点。
注意
- 要将一个副本提升为主节点,它必须首先被集群中大多数主节点知晓为副本。否则,它无法赢得故障转移选举。如果该副本刚刚被添加到集群中(见下文的添加新节点作为副本),您可能需要等待一段时间再发送`CLUSTER FAILOVER`命令,以确保集群中的主节点已经意识到这个新副本的存在。
#
添加一个新节点添加一个新节点基本上是添加一个空节点,然后将一些数据移入其中(如果它是一个新的主节点),或者告诉它设置为已知节点的副本(如果它是一个副本)。
我们将展示这两种情况,从添加一个新的主实例开始。
在这两种情况下,要执行的第一步是**添加一个空节点**。
这就像在端口 7006 上启动一个新节点一样简单(我们已经为现有的 6 个节点使用了 7000 到 7005),配置与其他节点相同,除了端口号。所以为了与我们之前节点使用的设置保持一致,您应该这样做:
- 在您的终端应用程序中创建一个新标签页。
- 进入 `cluster-test` 目录。
- 创建一个名为 `7006` 的目录。
- 在其中创建一个 keydb.conf 文件,与用于其他节点的文件类似,但使用 7006 作为端口号。
- 最后用 `../keydb-server ./keydb.conf` 启动服务器
此时服务器应该正在运行。
现在我们可以像往常一样使用**keydb-cli**将节点添加到现有集群中。
正如您所见,我使用了 **add-node** 命令,将新节点的地址指定为第一个参数,将集群中一个随机现有节点的地址指定为第二个参数。
实际上,keydb-cli 在这里帮助我们的很少,它只是向该节点发送了一条 `CLUSTER MEET` 消息,这也可以手动完成。然而,keydb-cli 在操作前会检查集群的状态,所以即使您知道内部工作原理,也建议始终通过 keydb-cli 执行集群操作。
现在我们可以连接到新节点,看看它是否真的加入了集群:
请注意,由于此节点已经连接到集群,它已经能够正确重定向客户端查询,并且通常来说是集群的一部分。然而,与其他主节点相比,它有两个特点:
- 它不持有任何数据,因为它没有分配哈希槽。
- 因为它是一个没有分配槽的主节点,所以当一个副本(显示为`slave`)想要成为主节点时,它不参与选举过程。
现在可以使用 `keydb-cli` 的重新分片功能为该节点分配哈希槽。展示这个基本上是无用的,因为我们已经在前一节做过了,没有区别,只是一个以空节点为目标的重新分片。
#
添加新节点作为副本添加一个新的副本可以通过两种方式进行。显而易见的一种是再次使用 keydb-cli,但带上 --cluster-slave 选项,像这样:
请注意,这里的命令行与我们用于添加新主节点的命令行完全相同,所以我们没有指定要将副本添加到哪个主节点。在这种情况下,keydb-cli 会将新节点作为副本添加到拥有最少副本的随机主节点之一。
然而,您可以使用以下命令行精确指定您希望新副本的目标主节点:
这样,我们就把新的副本分配给了一个特定的主节点。
一种更手动的方式来将副本添加到特定主节点是,先将新节点添加为空主节点,然后使用 `CLUSTER REPLICATE` 命令将其转换为副本。如果节点已被添加为副本,但您想将其移动为不同主节点的副本,此方法也适用。
例如,为了为当前服务于哈希槽范围 11423-16383 的节点 127.0.0.1:7005 添加一个副本,该节点的节点 ID 为 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e,我所需要做的就是连接到新节点(已经作为空主节点添加)并发送命令:
就是这样。现在我们为这组哈希槽有了一个新的副本,并且集群中的所有其他节点已经知道了(在几秒钟内更新它们的配置后)。我们可以用以下命令进行验证:
节点 3c3a0c... 现在有两个副本(显示为 `slave`),分别运行在端口 7002(现有的)和 7006(新的)上。
#
移除一个节点要移除一个副本节点,只需使用 keydb-cli 的 `del-node` 命令:
第一个参数只是集群中的一个随机节点,第二个参数是您要移除的节点的 ID。
您也可以用同样的方式移除一个主节点,**然而,为了移除一个主节点,它必须是空的**。如果主节点不是空的,您需要先将数据从它重新分片到所有其他主节点。
移除主节点的另一种方法是对其进行手动故障转移,将其转移到其副本之一,然后在该节点变成新主节点的副本后移除它。显然,当您想减少集群中实际主节点数量时,这没有帮助,在这种情况下,需要进行重新分片。
#
副本迁移在 KeyDB 集群中,可以随时使用以下命令将副本重新配置为与不同的主节点进行复制:
然而,有一种特殊情况,您希望副本在没有系统管理员帮助的情况下自动从一个主节点移动到另一个主节点。副本的自动重新配置称为*副本迁移*,它能够提高 KeyDB 集群的可靠性。
注意:您可以在KeyDB 集群规范中阅读副本迁移的详细信息,这里我们只提供一些关于总体思路以及您应该如何做才能从中受益的信息。
您可能希望让集群副本在特定条件下从一个主节点移动到另一个主节点的原因是,通常 KeyDB 集群对故障的抵抗能力与附加到给定主节点的副本数量有关。
例如,一个每个主节点都有单个副本的集群,如果主节点及其副本同时发生故障,则无法继续操作,因为没有其他实例拥有该主节点所服务的哈希槽的副本。然而,虽然网络分裂很可能同时隔离多个节点,但许多其他类型的故障,如单个节点本地的硬件或软件故障,是一类非常显著且不太可能同时发生的故障。因此,在您的集群中,每个主节点都有一个副本,可能副本在凌晨 4 点被终止,而主节点在早上 6 点被终止。这仍将导致集群无法再运行。
为了提高系统的可靠性,我们可以选择为每个主节点添加额外的副本,但这成本高昂。副本迁移允许只为少数主节点添加更多副本。因此,假设您有 10 个主节点,每个主节点有 1 个副本,总共有 20 个实例。但是,您可以再添加 3 个实例作为某些主节点的副本,这样某些主节点就会有多个副本。
通过副本迁移,如果一个主节点没有副本了,那么拥有多个副本的主节点中的一个副本将会迁移到这个“孤立”的主节点。因此,在我们的例子中,当您的副本在凌晨4点宕机后,另一个副本将取代它的位置,而当主节点在凌晨5点也发生故障时,仍然有一个副本可以被选举出来,从而使集群可以继续运行。
那么,关于副本迁移,您应该简要了解些什么?
- 集群会尝试从当前拥有最多副本的主节点迁移一个副本。
- 要从副本迁移中受益,您只需在集群中的单个主节点上添加一些额外的副本,具体是哪个主节点并不重要。
- 有一个配置参数可以控制副本迁移功能,叫做 `cluster-migration-barrier`:您可以在 KeyDB Cluster 提供的示例 `keydb.conf` 文件中阅读更多相关信息。
#
升级 KeyDB 集群中的节点升级副本节点很简单,因为您只需要停止节点并用更新版本的 KeyDB 重新启动它。如果有客户端使用副本节点进行读扩展,如果某个副本不可用,它们应该能够重新连接到另一个副本。
升级主节点要复杂一些,建议的步骤是:
- 使用 `CLUSTER FAILOVER` 触发主节点到其副本之一的手动故障转移。(请参阅本文档中的手动故障转移部分。)
- 等待主节点转变为副本。
- 最后,像升级副本一样升级该节点。
- 如果您希望主节点是您刚刚升级的那个节点,请触发一次新的手动故障转移,以将升级后的节点变回主节点。
按照这个步骤,你应该一个接一个地升级节点,直到所有节点都升级完毕。
#
迁移到 KeyDB 集群愿意迁移到 KeyDB 集群的用户可能只有一个主节点,或者可能已经在使用预先存在的分片设置,其中键在 N 个节点之间分割,使用一些内部算法或由其客户端库或 KeyDB 代理实现的分片算法。
在这两种情况下,都可以轻松地迁移到 KeyDB 集群,然而最重要的细节是应用程序是否使用了多键操作,以及如何使用。有三种不同的情况:
- 不使用多键操作、事务或涉及多键的 Lua 脚本。键是独立访问的(即使是通过将多个关于同一键的命令组合在一起的事务或 Lua 脚本访问)。
- 使用多键操作、事务或涉及多键的 Lua 脚本,但仅限于具有相同**哈希标签**的键,这意味着一起使用的键都有一个恰好相同的`{...}`子字符串。例如,以下多键操作是在相同哈希标签的上下文中定义的:`SUNION {user:1000}.foo {user:1000}.bar`。
- 使用多键操作、事务或涉及多键的 Lua 脚本,但键名没有明确的或相同的哈希标签。
第三种情况 KeyDB 集群无法处理:需要修改应用程序,以不使用多键操作,或者只在相同哈希标签的上下文中使用它们。
情况 1 和 2 均已覆盖,因此我们将重点关注这两种情况,它们的处理方式相同,因此在文档中不做区分。
假设您已有的数据集分布在 N 个主节点上,如果之前没有分片,则 N=1。要将您的数据集迁移到 KeyDB 集群,需要以下步骤:
- 停止您的客户端。目前无法自动进行到 KeyDB 集群的实时迁移。您或许可以在您的应用程序/环境的上下文中编排一次实时迁移。
- 使用 BGREWRITEAOF 命令为您所有的 N 个主节点生成一个仅追加文件(AOF),并等待 AOF 文件完全生成。
- 将您的 AOF 文件从 aof-1 到 aof-N 保存到某个地方。此时,如果您愿意,可以停止您的旧实例(这很有用,因为在非虚拟化部署中,您通常需要重用相同的计算机)。
- 创建一个由 N 个主节点和零个副本组成的 KeyDB 集群。您稍后会添加副本。确保所有节点都使用仅追加文件进行持久化。
- 停止所有集群节点,用您预先存在的仅追加文件替换它们的仅追加文件,aof-1 用于第一个节点,aof-2 用于第二个节点,直到 aof-N。
- 用新的 AOF 文件重新启动您的 KeyDB 集群节点。它们会抱怨说,根据它们的配置,有一些键不应该存在。
- 使用 `keydb-cli --cluster fix` 命令来修复集群,以便根据每个节点是否有权限来迁移键。
- 最后使用 `keydb-cli --cluster check` 确保您的集群是正常的。
- 重新启动您修改过的、使用支持 KeyDB 集群的客户端库的客户端。
还有一种将数据从外部实例导入到 KeyDB 集群的替代方法,即使用 `keydb-cli --cluster import` 命令。
该命令将一个正在运行的实例的所有键(从源实例中删除键)移动到指定的预先存在的 KeyDB 集群中。