Ring-builder

使用 swift-ring-builder 工具构建和管理 ring。该工具将分区分配给设备,并将优化的 Python 结构写入 gzip 压缩的序列化文件到磁盘上,以便传输到服务器。服务器进程会定期检查文件的修改时间,并在需要时重新加载内存中的 ring 结构副本。如果您使用略旧版本的 ring,由于 ring-builder 管理 ring 变更的方式,某个分区子集的三个副本中的一个将不正确。您可以规避此问题。

Ring-builder 还会保留其自身的 builder 文件,其中包含 ring 信息以及构建未来 ring 所需的额外数据。务必保留这些 builder 文件的多个备份副本。一种选择是将 builder 文件复制到每台服务器,同时复制 ring 文件本身。另一种是将 builder 文件上传到集群本身。如果您丢失了 builder 文件,则必须从头开始创建新的 ring。几乎所有分区都将被分配给不同的设备,因此,几乎所有存储的数据都必须复制到新的位置。因此,从 builder 文件丢失中恢复是可能的,但数据在一段时间内将无法访问。

Ring 数据结构

Ring 数据结构由三个顶级字段组成:集群中设备的列表、指示分区到设备分配的设备 ID 列表列表,以及一个指示计算哈希分区需要将 MD5 哈希移动多少位的整数。

分区分配列表

这是一个 array('H') 设备 ID 的列表。最外层列表包含每个副本的 array('H')。每个 array('H') 的长度等于 ring 的分区数。 array('H') 中的每个整数是上述设备列表中的索引。Ring 类内部将分区列表称为 _replica2part2dev_id

因此,要创建分配给分区的设备字典列表,Python 代码如下所示

devices = [self.devs[part2dev_id[partition]] for
part2dev_id in self._replica2part2dev_id]

这段代码有点简单,因为它没有考虑到删除重复设备的因素。如果 ring 的副本数多于设备数,则某个分区将在一个设备上拥有多个副本。

array('H') 用于节省内存,因为可能存在数百万个分区。

超载

Ring builder 尝试在尊重设备权重的同时,尽可能地将副本彼此分开。当它无法同时做到这两点时,超载因子决定了会发生什么。每个设备承担其所需分区额外份额,以允许副本分散;在该额外份额耗尽后,副本会比最佳情况更接近地放置在一起。

超载因子允许操作员在副本分散(持久性)和数据分散(均匀磁盘使用率)之间进行权衡。

默认超载因子为 0,因此严格遵循设备权重。

如果超载因子为 0.1,则每个设备接受比其原本应接受的 10% 更多的分区,但仅当它需要维护分区分散时。

例如,考虑一个由具有相同大小磁盘的机器组成的 3 个节点集群;节点 A 有 12 个磁盘,节点 B 有 12 个磁盘,节点 C 有 11 个磁盘。ring 的超载因子为 0.1(10%)。

如果没有超载,某些分区最终将只在节点 A 和 B 上拥有副本。但是,有了超载,每个设备都可以接受最多 10% 的额外分区以实现分散。C 中缺失的磁盘意味着有相当于一个磁盘的分区需要在剩余的 11 个磁盘上分散,这使得 C 中的每个磁盘的额外负载为 9.09%。由于这小于 10% 的超载,因此每个分区在每个节点上都有一个副本。

但是,这意味着节点 C 中的磁盘比节点 A 和 B 中的磁盘拥有更多数据。如果 80% 满是集群的警告阈值,则节点 C 的磁盘达到 80% 满,而 A 和 B 的磁盘仅为 72.7% 满。

副本计数

为了支持副本计数的逐步变化,ring 可以拥有一个实数副本,而不受整数副本的限制。

分数副本计数适用于整个 ring,而不适用于单个分区。它指示每个分区的平均副本数。例如,副本计数为 3.2 意味着 20% 的分区具有四个副本,而 80% 的分区具有三个副本。

副本计数是可调整的。例如

$ swift-ring-builder account.builder set_replicas 4
$ swift-ring-builder account.builder rebalance

您必须在全局分布式集群中重新平衡副本 ring。这些集群的运营商通常希望在每个区域拥有相同数量的副本和区域。因此,当运营商添加或删除区域时,运营商会添加或删除副本。删除不必要的副本可以节省磁盘成本。

您可以以不会对集群性能产生不利影响的速率逐步增加副本计数。例如

$ swift-ring-builder object.builder set_replicas 3.01
$ swift-ring-builder object.builder rebalance
<distribute rings and wait>...

$ swift-ring-builder object.builder set_replicas 3.02
$ swift-ring-builder object.builder rebalance
<distribute rings and wait>...

更改在 ring 重新平衡后生效。因此,如果您打算从 3 个副本更改为 3.01,但您不小心输入了 2.01,则不会丢失任何数据。

此外,swift-ring-builder X.builder create 命令现在可以接受副本数量的十进制参数。

分区偏移值

分区偏移值在 Ring 类内部称为 _part_shift。此值用于移动 MD5 哈希以计算应将哈希数据存储在哪个分区。仅使用哈希的顶部四个字节在此过程中。例如,要使用 Python 计算 /account/container/object 路径的分区

partition = unpack_from('>I',
md5('/account/container/object').digest())[0] >>
self._part_shift

对于使用 part_power P 生成的 ring,分区偏移值为 32 - P

构建 ring

Ring builder 过程包括以下高级步骤

  1. 该工具根据设备的权重计算分配给每个设备的的分区数。例如,对于 20 次幂的分区,ring 有 1,048,576 个分区。一千个具有相同权重的设备每个都需要 1,048.576 个分区。这些设备按它们想要的分区数排序,并在整个初始化过程中保持排序。

    注意

    每个设备还会被分配一个随机平局破坏值,当两个设备想要相同数量的分区时,该值用于在两个设备想要相同数量的分区时使用。此平局破坏值不会存储在磁盘上的任何位置,因此使用相同参数创建的两个不同的 ring 将具有不同的分区分配。对于可重复的分区分配,RingBuilder.rebalance() 接受一个可选的种子值,该种子值会播种 Python 伪随机数生成器。

  2. Ring builder 将每个分区副本分配给在该点需要最多分区的设备,同时尽可能地将其与其它副本分开。Ring builder 优先将副本分配给尚未拥有副本的区域中的设备。如果不可用此类区域,Ring builder 将搜索不同区域或不同服务器上的设备。如果找不到,它将查找没有副本的设备。最后,如果所有选项都用尽,Ring builder 将将副本分配给已分配最少副本的设备。

    注意

    Ring builder 仅当 ring 的副本数多于设备数时,才会将多个副本分配给一个设备。

  3. 从旧 ring 构建新 ring 时,Ring builder 会重新计算每个设备想要的分区所需数量。

  4. Ring builder 取消分配分区并收集这些分区以进行重新分配,如下所示

    • Ring builder 取消分配从任何已删除设备分配的任何分区,并将这些分区添加到收集的列表中。

    • Ring builder 取消分配可以分散以提高持久性的任何分区副本,并将这些分区添加到收集的列表中。

    • Ring builder 取消分配任何拥有比其需要的分区更多的设备的随机分区,并将这些分区添加到收集的列表中。

  5. Ring builder 使用类似于先前描述的方法将收集的分区重新分配给设备。

  6. 当 Ring builder 将副本重新分配给分区时,Ring builder 会记录重新分配的时间。Ring builder 在收集用于重新分配的分区时使用此值,以便在可配置的时间段内不会移动两次分区。RingBuilder 类将此可配置的时间段称为 min_part_hours。对于已删除设备上的分区的副本,Ring builder 会忽略此限制,因为设备删除仅发生在设备故障时,并且重新分配是唯一的选择。

这些步骤由于收集用于重新分配的分区的随机性,并不总是能完美地重新平衡 ring。为了帮助达到更平衡的 ring,重新平衡过程会重复进行,直到接近完美(偏差小于 1%)或平衡不再改善至少 1%(表明由于区域严重失衡或最近移动了太多分区而我们可能无法获得完美平衡)。