This work is licensed under a Creative Commons Attribution 3.0
Unported License.
http://creativecommons.org/licenses/by/3.0/legalcode

增加环分区能力

本文档描述了一个过程和对 Swift 代码的修改,它们共同实现了在不中断集群运行的情况下增加环分区能力。

Swift 运营商有时在部署 Swift 时选择一个环分区能力,随后又希望更改分区能力

  1. 运营商选择的分区能力后来证明太小,从而限制了他们重新平衡不断增长的集群的能力。
  2. 也许更可能的是,为了避免上述问题,运营商选择的分区能力后来证明不必要地大,并随后希望减少它。

本提案通过增加分区能力直接解决了第一个问题。尽管它没有直接解决第二个问题(即它不能减少环能力),但它通过消除在首次部署集群时选择大分区能力的动机,间接有助于避免该问题。

问题描述

环能力决定了资源(帐户、容器或对象)映射到的分区。分区包含在资源存储在后端文件系统中的路径中。因此,更改分区能力需要将资源重新定位到后端文件系统中的新路径。

在人口稠密的集群中,重新定位过程可能非常耗时,因此为了避免停机时间,最好在集群仍在运行时重新定位资源。但是,必须这样做,以免(临时)丢失数据访问权限,并且不影响复制和审计等进程的性能。

提议的变更

概述

所提出的解决方案在分区能力更改期间避免复制任何文件内容。对象从其当前分区“移动”到新分区,但当前分区和新分区被安排在同一设备上,因此“移动”是通过使用文件系统链接而不是复制数据来实现的。

(很可能增加分区能力的动机是为了允许重新平衡环。任何重新平衡都将在分区能力增加完成后进行 - 在分区能力更改期间,环平衡不会改变。)

为了允许集群在分区能力更改期间继续运行(特别是为了避免复制器和审计器进程的任何中断或不正确行为),新分区目录是在与当前分区目录分开的文件系统分支中创建的。当所有新分区目录都已填充时,环将转换为使用新的文件系统分支。

在此转换期间,对象服务器维护从当前和新分区目录到资源文件的链接。但是,如前所述,没有文件内容被复制或重复。旧分区目录最终会被删除。

详细描述

更改环的分区能力的过程包括三个阶段

  1. 准备 - 在此阶段,当前分区目录继续使用,但现有资源也链接到新分区目录,以预期新的环分区能力。
  2. 切换 - 在此阶段,环转换为使用新分区目录;代理和后端服务器滚动使用新的环分区能力。
  3. 清理 - 一旦所有服务器都使用新的环分区能力,旧分区目录中的资源文件将被删除。

为简单起见,我们以对象环为例描述每个阶段的详细信息,但请注意,相同的过程可以应用于帐户和容器环以及服务器。

准备阶段

在准备阶段,环文件中设置了两个新属性

  • 环的 epoch:如果尚未设置,则向环中添加一个新的 epoch 属性。环的 epoch 用于确定分区目录的父目录。类似于环的策略索引附加到 objects 目录名称的方式,epoch 将作为前缀添加到 objects 目录名称。为简单起见,环的 epoch 将是一个从 0 开始单调递增的整数。没有 epoch 属性的“遗留”环将被视为具有 epoch 0。
  • next_part_power 属性指示将在环的下一个 epoch 中使用的分区能力。next_part_power 属性在准备阶段用于确定对象应存储在环的下一个 epoch 中的分区目录。

此时,环文件没有进行其他更改:当前分区能力和分区到设备的映射保持不变。

更新后的环文件分发到所有服务器。在此准备阶段,代理服务器将继续使用当前环分区映射来确定对象的后端 URL。对象服务器以及复制器和审计器进程也继续使用当前环参数。但是,在 PUT 和 DELETE 操作期间,对象服务器将创建指向对象未来分区目录中对象文件的额外链接,以准备最终切换到环的下一个 epoch。这不需要任何额外的对象内容复制或写入。

未来分区目录的文件系统路径确定如下。通常,对象服务器文件系统上对象文件的路径形式为

dev/[<epoch>-]objects[-<policy>]/<partition>/<suffix>/<hash>/<ts>.<ext>

其中

  • epoch 是环的 epoch,如果非零
  • policy 是对象容器的策略索引,如果非零
  • dev 是环文件将 partition 映射到的设备
  • partition 是对象的分区,使用 partition = F(hash) >> (32 - P) 计算,其中 P 是环分区能力
  • suffixhash 的最后三位数字
  • hash 是对象名称的哈希
  • ts 是对象时间戳
  • ext 是文件扩展名(datametats

给定环文件中的 next_part_powerepoch,可以计算

future_partition = F(hash) >> (32 - next_part_power)
next_epoch = epoch + 1

未来的分区目录是

dev/<next_epoch>-objects[-<policy>]/<next_partition>/<suffix>/<hash>/<ts>.<ext>

例如,考虑一个处于第一个 epoch 的环,当前分区能力为 P,其中包含当前在分区 X 中的对象,其中 0 <= X < 2**P。如果分区能力增加 2 倍,则对象在环的下一个 epoch 中的未来分区将是 2X 或 2X+1。在 DELETE 期间,将在以下位置之一创建额外的文件系统链接:

dev/1-objects/<2X>/<suffix>/<hash>/<ts>.ts
dev/1-objects/<2X+1>/<suffix>/<hash>/<ts>.ts

一旦对象服务器被确认正在使用更新后的环文件,就会启动一个新的 relinker 进程。relinker 通过爬取文件系统并将现有对象链接到未来分区目录,为分区能力更改准备对象服务器的文件系统。relinker 以与上述对象服务器相同的方式确定每个对象的未来分区目录。

relinker 不会从当前分区目录中删除链接。一旦 relinker 成功完成,每个现有对象都应该从当前分区目录和未来分区目录中链接。任何后续的对象 PUT 或 DELETE 都将反映在当前和未来分区目录中,如上所述。

为了避免新创建的对象“丢失”,在 relinker 进程开始之前,对象服务器使用更新后的环文件非常重要,以确保对象服务器或 relinker 为每个对象创建未来分区链接。这可能需要在 relinker 进程启动之前重新启动对象服务器,或者以其他方式报告它们已重新加载环文件。

relinker 将在文件 /var/cache/swift/relinker.recon 中报告成功完成,可以通过(修改后的)recon 中间件查询该文件。

一旦 relinker 进程在所有对象服务器上成功完成,分区能力更改过程就可以进入切换阶段。

切换阶段

为了开始切换到使用下一个分区能力,环文件再次更新

  • 当前分区能力存储为 previous_part_power
  • 当前分区能力设置为 next_partition_power
  • next_partition_power 设置为 None
  • 环的 epoch 递增
  • 分区到设备的映射重新创建,使得分区 2X 和 2X+1 映射到与分区 X 在上一个 epoch 中映射到的相同设备。这是一个简单的转换。由于对象内容未在设备之间移动,因此实际的环平衡保持不变。

然后,更新后的环文件分发到所有代理和对象服务器。

由于环文件分发和加载不是即时的,因此存在一个时间窗口,在此期间代理服务器可以将对象请求指向旧分区或当前分区(请注意,以前称为“未来”的分区现在称为“当前”)。因此,对象服务器将在 PUT 和 DELETE 请求期间创建额外的文件系统链接,从旧分区目录指向当前分区目录中的文件。旧分区目录的路径的确定方式与准备阶段中确定未来分区目录的方式相同,但现在使用 previous_part_power 并递减当前环 epoch

这意味着,如果一个代理使用当前分区 PUT 一个对象,然后另一个代理随后尝试使用旧分区 GET 该对象,则该对象将被找到,因为当前分区和旧分区都映射到同一设备。同样,如果一个代理使用旧分区 PUT 一个对象,然后另一个代理使用当前分区 GET 该对象,则该对象将在对象服务器上的当前分区中找到。

对象审计器和复制器进程重新启动,以强制重新加载环文件并开始使用当前环参数运行。

清理阶段

一旦所有服务器都被确认正在使用更新后的环文件,清理阶段即可开始。同样,这可能需要重新启动服务器或报告它们已在切换期间重新加载环文件。

环文件进行最终更新:previous_partition_power 属性设置为 None,并且环文件再次分发。一旦对象服务器重新加载更新后的环文件,它们将停止在旧分区目录中创建对象文件链接。

此时,旧分区目录可以删除 - 在删除旧分区中的对象时无需创建墓碑文件,因为这些分区目录不再被任何 Swift 进程使用。

清理进程将爬取文件系统并删除任何不属于当前 epoch 或未来 epoch 的分区目录。此清理进程应定期重复,以防在分区能力更改期间离线的任何设备重新联机 - 可以在这些设备上发现的旧 epoch 分区目录可以删除。正常复制可能导致在复活的磁盘上创建当前 epoch 分区目录。

(清理功能可以添加到现有进程,例如审计器)。

其他注意事项

swift-dispersion-[populate|report]

swift-dispersion-[populate|report] 工具将需要感知 epoch。增加分区能力后,可能需要运行 swift-dispersion-populate 来实现所需的覆盖范围。(尽管最初设备覆盖范围将保持不变,但分区覆盖的百分比将按分区能力增加的任何因子减少。)

审计

在准备和切换期间,审计器可能会发现损坏的对象。隔离目录不在 epoch 分区目录文件系统分支中,因此当旧分区被删除时,被隔离的对象不会丢失。

在当前分区目录中隔离对象不会从未来分区中移除该对象,因此在切换后,审计器将再次发现该对象,并再次隔离它。磁盘文件隔离重命名器可以选择性地感知“relinker”,并在隔离对象时取消链接重复的对象引用。

备选方案

前期工作

swift_ring_tool 允许在 Swift 服务禁用时增加环能力。它采取与本提案类似的方法,即更改环映射,使每个资源在移动到新分区时仍保留在同一设备上。但是,新分区是在与现有分区相同的文件系统分支中创建的(因此需要在重新定位期间暂停服务)。

之前已向 Swift 上游提出了提案

https://bugs.launchpad.net/swift/+bug/933803 建议进行“同设备”分区重新映射,正如本提案一样,但没有提供将资源重新定位到新分区目录的方案。

https://review.openstack.org/#/c/21888/ 建议为每个设备维护一个分区能力(因此只有新设备使用增加的分区能力),但由于复制的复杂性,似乎已被放弃。

在现有 objects[-policy] 目录中创建未来分区

在准备阶段,对象的文件系统条目的重复和(可能重复的)分区的创建,如果它们没有被隔离在另一个文件系统分支中,可能会对其他后端进程产生不良影响。

例如,对象复制器可能会发现新创建的未来分区目录似乎“放错了位置”。复制器将尝试将这些同步到它们的主节点(根据旧的环映射),这是不必要的。更糟糕的是,复制器可能会从其当前节点中删除未来分区,从而撤销 relinker 进程的工作。

如果复制器从准备阶段一开始就采用未来环映射,那么对于现在看起来放错了位置的当前分区也会出现相同的问题。此外,复制过程很可能会在远程节点上与 relinker 进程竞争填充未来分区:如果节点 A 上的重新定位速度快于节点 B,则复制器可能会开始将对象从 A 同步到 B,这又是不必要的且代价高昂的。

审计器也将受到影响,因为它会在未来分区目录中发现对象并对其进行审计,无法将其与仍存储在当前分区中的对象区分开来。

当然,可以通过在准备阶段禁用复制和审计来避免这些问题,但我们建议使未来环分区命名与当前环分区命名互斥,并且简单地限制复制器和审计器只处理当前环分区集中的分区。换句话说,我们将这些进程与 relinker 正在创建的未来分区目录隔离开来。

在现有 objects 目录中使用互斥的未来分区

当前计算对象分区的算法是计算对象的 32 位哈希,然后使用其 P 个最高有效位,从而得到范围为 {0, 2**P - 1} 的分区。即

part = H(object name) >> (32 - P)

分区能力为 P+1 的环将重用分区能力为 P 的环的所有分区号。

为了消除未来环分区与当前环分区的重叠,我们可以更改分区号算法,在环的分区能力增加时为每个分区号添加一个偏移量

偏移量 = 2**P 分区 = (H(对象名称) >> (32 - P)) + 偏移量

这与向后兼容:如果环文件中未定义 offset,则将其设置为零。

为了确保分区号保持 < 2**32,此更改会将最大分区能力从 32 降低到 31。

代理服务器在重新定位阶段开始时使用新环

这将意味着对后端进行 GET 操作时,对象 URL 将使用新环分区。对象可能尚未重新定位到其新分区目录,因此对象服务器将需要回退到旧环分区中查找对象。对新分区的 PUT 和 DELETE 操作将需要以旧位置不存在更新的对象时间戳为条件。这比建议的方法更复杂。

启用分区能力降低

使用本提案中提出的方法,环能力降低不容易实现,因为无法保证当前 epoch 中将合并到下一个 epoch 中的分区位于同一设备上。因此,在准备阶段,文件内容很可能需要在设备之间复制。

实现

负责人

主要负责人
alistair.coles@hp.com

工作项

  1. 修改环类以支持新属性
  2. 修改环构建器以管理新属性
  3. 修改后端服务器以在未来 epoch 分区目录中复制文件链接
  4. 使后端服务器和 relinker 以 recon 可以报告的方式报告其状态,例如服务器报告新环 epoch 何时已加载,relinker 报告所有重新链接何时已完成。
  5. 使 recon 支持报告这些状态
  6. 修改假定存储目录为 objects[-policy_index] 的代码,使其感知 epoch 前缀
  7. 使 swift-dispersion-populate 和 swift-dispersion-report 感知 epoch
  8. 实现 relinker 守护进程
  9. 记录过程

仓库

不会创建新的 git 存储库。

服务器

没有创建新服务器。

DNS 条目

没有创建或更新 DNS 条目。

文档

过程将在管理员指南中记录。环构建器文档将添加内容。

安全性

未预见到任何安全问题。

测试

将为环构建器、环类和对象服务器的更改添加单元测试。

将需要探测测试来验证增加环能力的过程。

功能测试将保持不变。

依赖项