槽迁移概述
在 Garnet 中,槽迁移描述了在完全运行的集群中,将槽所有权重新分配并将其关联的键值对从一个主节点传输到另一个主节点的过程。此操作允许在添加或删除节点时,实现集群内高效的资源利用和负载均衡。迁移操作仅在集群模式下可用。槽迁移可以由给定槽的所有者(称为源节点)发起,并指向另一个已知且受信任的主节点(称为目标节点)。实际的数据迁移可以通过使用MIGRATE
命令启动,该命令以两种模式运行:(1)迁移单个键(2)迁移整个槽或槽范围。本页面重点介绍槽迁移的实现细节。有关使用关联命令的更多信息,请参阅槽迁移用户指南。
实现细节
迁移操作的实现分为两个组件
- 命令解析和验证组件。
- 迁移会话操作和管理组件。
第一个组件负责解析和验证迁移命令参数。验证包括以下内容
- 解析每个迁移选项并验证与该选项关联的参数。
- 验证即将迁移的关联槽的所有权。
- 对于
KEYS
选项,验证要迁移的键没有跨不同的槽进行哈希。 - 对于
SLOTS/SLOTSRANGE
选项,验证没有槽被多次引用。 - 对于
SLOTSRANGE
选项,确保范围由一对值定义。 - 验证迁移目标已知、受信任且具有主节点的角色。
解析成功完成后,会创建并执行一个迁移会话。根据所选选项,迁移会话作为前台任务(使用KEYS
选项)或后台任务(SLOTS/SLOTSRANGE
选项)执行。
第二个组件分为以下子组件
MigrationManager
MigrateSessionTaskStore
MigrateSession
MigrationManager
负责管理活动的MigrateSession
任务。它使用MigrateSessionTaskStore
原子地添加或删除MigrateSession
的新实例。它还负责通过检查每个会话中引用的槽是否冲突来确保现有会话与即将添加的会话不冲突。最后,它提供了正在进行的迁移任务数量的信息。
迁移数据完整性
由于槽迁移可以在 Garnet 集群运行时启动,因此需要仔细协调,以避免客户端尝试写入新数据时出现任何数据完整性问题。此外,无论提出何种解决方案,都不应在迁移进行时阻碍数据可用性。
我们的实现利用基于槽分片的概念,以确保在迁移任务处于活动状态时,映射到相关槽的键无法修改。这是通过将源节点中的槽状态设置为 MIGRATING 来实现的。这会阻止任何写入请求,但仍允许读取。写入请求可以使用 ASKING 发送到目标节点,但如果使用此选项,Garnet 不提供一致性保证。Garnet 保证在常规迁移(即使用 ASKING)期间不会丢失任何键。
由于 Garnet 在多线程环境中运行,因此从STABLE
到MIGRATING
的转换必须安全进行,以便每个线程都有机会观察该状态变化。这可以通过纪元保护来实现。具体来说,当一个槽转换为新状态时,实现实际状态转换的段在进行更改后必须自旋等待,并且只有在所有活动的客户端会话都已移动到下一个纪元后才能返回。
下面显示了与槽状态转换期间纪元保护相关的代码片段。每个客户端会话在处理传入数据包之前,将首先获取当前纪元。对于发出槽状态转换命令(即 CLUSTER SETSLOT)的客户端会话也是如此。成功更新本地配置命令后,前一个命令的处理线程将执行以下操作
- 释放当前纪元(以避免等待自身进行转换)。
- 自旋等待,直到所有线程转换到下一个纪元。
- 重新获取下一个纪元。
这一系列步骤确保在返回时,处理线程向命令发起者(如果状态转换在内部发生,则向方法调用者)保证状态转换对所有活动的线程都是可见的。因此,任何活动的客户端会话都将考虑新的槽状态来处理后续命令。
loading...
在迁移期间,从源节点的角度来看,槽状态的改变(即MIGRATING
)是瞬态的。这意味着在迁移完成之前,槽仍然由源节点拥有。然而,由于需要生成-ASK
重定向消息,因此workerId被设置为目标节点的workerId,而不增加当前本地纪元(以避免将此瞬态更新传播到整个集群)。因此,根据正在执行的操作的上下文,可以通过访问workerId属性来确定节点的实际所有者,而迁移的目标节点则通过_workerId变量确定。例如,CLUSTER NODES
将使用workerId属性(通过 GetSlotRange(workerId)),因为它必须返回节点的实际所有者,即使在迁移期间也是如此。同时,它需要返回所有处于MIGRATING
或IMPORTING
状态的节点以及与该状态关联的节点 ID,这可以通过检查_workerId变量(通过 GetSpecialStates(workerId))来完成。
loading...
迁移 KEYS 实现细节
使用KEYS
选项,Garnet 将遍历提供的键列表,并将它们分批迁移到目标节点。使用此选项时,迁移命令的发出者必须确保源节点和目标节点中的槽状态设置正确。此外,发出者必须在迁移完成之前,在一次MIGRATE
调用中或多次调用中提供映射到特定槽的所有键。当所有键值对都已迁移到目标节点时,发出者必须重置槽状态并将槽的所有权分配给新节点。
MigrateKeys
是使用KEYS
选项进行迁移操作的主要驱动程序。此方法遍历提供的键列表并将它们发送到目标节点。这发生在以下两个阶段
- 找到
MIGRATE
命令提供的所有键并将它们发送到目标节点。 - 对于在主存储中未找到的所有剩余键,在对象存储中查找,如果找到则将它们发送到目标节点。给定键可能无法从任一存储中检索,因为它可能已过期。在这种情况下,执行继续到下一个可用键,并且不会引发特定错误。数据传输完成后,根据是否启用了 COPY 选项,
MigrateKeys
会从两个存储中删除这些键。
loading...
迁移 SLOTS 细节
SLOTS
或SLOTSRANGE
选项使 Garnet 能够迁移一组槽以及映射到这些槽的所有关联键。这些选项与KEYS
选项有以下几点不同
- 无需具体了解正在迁移的单个键以及它们如何映射到关联的槽。用户只需提供槽号即可。
- 状态转换完全在服务器端处理。
- 为了完成迁移操作,我们必须扫描主存储和对象存储,以查找并迁移与给定槽关联的所有键。
基于上面最后一项,使用SLOTS
或SLOTSRANGE
的迁移操作可能看起来更昂贵,特别是如果正在迁移的槽只包含少数键。然而,与KEYS
选项相比,它通常更便宜,因为KEYS
选项需要客户端和服务器之间多次往返(以便任何相关键都可以用作MIGRATE
命令的输入),此外还需要对两个存储进行全面扫描。
如下面代码片段所示,MIGRATE SLOTS
任务将通过依赖于前面描述的纪元保护机制,安全地将远程节点配置中的槽状态转换为IMPORTING
,并将本地节点配置中的槽状态转换为MIGRATING
。随后,它将开始分批将数据迁移到目标节点。数据迁移完成后,任务将通过执行下一个槽状态转换来结束,其中正在迁移的槽的所有权将移交给目标节点。通过提高本地节点的配置纪元(即使用 RelinquishOwnership 命令),槽所有权交换将对整个集群可见。最后,源节点将向目标节点发出CLUSTER SETSLOT NODE
,以明确使其成为相应槽集合的所有者。最后一步不是必需的,仅用于加快配置传播。
loading...