Swift 内部原理:Ring

这是本系列文章的第一篇,总结了我们对 Swift 架构的分析。我们试图强调官方文档中不够清晰的一些要点。我们的主要基础是对源代码的深入研究。

以下内容适用于 Swift 版本 1.4.6。

Ring 是 Swift 架构中至关重要的部分。这个半数据库、半配置文件记录了集群中所有数据的位置。对于集群中存储的任何实体的每个可能路径,Ring 都指向特定物理节点上的特定设备。

Swift 识别三种类型的实体:账户、容器和对象。每种类型都有自己的 Ring,但所有三个 Ring 的构建方式都相同。Swift 服务使用相同的源代码来创建和查询所有三个 Ring。两个 Swift 类负责这些任务:RingBuilderRing

Ring 数据结构

Swift 中的每个 Ring 由 3 个元素组成:

  • 集群中的设备列表,也称为devsRing类中;
  • 一个设备 ID 列表的列表,指示分区到数据的分配,存储在名为_replica2part2dev_id;
  • 的变量中;以及一个整数,表示 MD5 哈希后的账户/容器/对象路径需要右移多少位来计算该哈希的分区索引(分区移位值,part_shift).
设备列表

设备列表包含 Ring 所知的全部存储设备(磁盘)。此列表的每个元素都是一个具有以下结构的字典:

类型
id 整数 设备列表的索引
zone 整数 设备所在的区域
weight 浮点数 设备在 Ring 中相对于其他设备的权重
ip 字符串 包含设备的服务器 IP 地址
port 整数 服务器用于处理设备请求的 TCP 端口
device 字符串 主机系统中的设备名称,例如sda1。它用于识别主机系统下的挂载点/srv/node
meta 字符串 用于存储设备任意信息的通用字段。服务器不直接使用。

可以使用列表中的值执行一些设备管理。首先,对于已移除的设备,'id'值设置为'None'。设备 ID 通常不重复使用。其次,将'weight'设置为 0.0 会临时禁用该设备,因为不会将任何分区分配给该设备。

分区分配列表

此数据结构是一个包含 N 个元素的列表,其中 N 是集群的副本计数。默认副本计数为 3。分区分配列表的每个元素都是一个array('H'),即 Python 紧凑高效的短无符号整数数组。这些值实际上是设备列表(见上一节)的索引。因此,分区分配列表中的每个array('H')元素都表示分区到设备的映射。

Ring 从路径的 MD5 哈希中提取可配置数量的位,并将其转换为一个长整型。这个数字用作array('H')的索引。该索引指向一个数组元素,该元素指定了分区映射到的设备的 ID。从哈希中保留的位数称为分区幂,2 的分区幂表示分区数。

对于给定的分区号,每个副本的设备不会与任何其他副本的设备位于同一区域。区域可用于根据物理位置、电源分离、网络分离或任何其他可能导致多个副本同时不可用的属性对设备进行分组。

分区移位值

这是从'/account/[container/[object]]'路径的 MD5 哈希中提取的位数,用于计算该路径的分区索引。分区索引通过将哈希的二进制部分转换为整数来计算。

Ring 操作

上述结构以 pickled(参见 Python pickle)和 gzipped(参见 Python gzip.GzipFile)文件的形式存储。共有三个文件,每个 Ring 一个,文件名通常是

account.ring.gzcontainer.ring.gzobject.ring.gz

。这些文件必须存在于每个 Swift 集群节点(包括 Proxy 和 Storage)的/etc/swift目录下,因为这些节点上的服务都使用它来定位集群中的实体。此外,集群所有节点上的 Ring 文件内容必须相同,以便集群正常运行。

Swift 内部没有机制可以保证 Ring 的一致性,即 gzip 文件未损坏且可读。Swift 服务无法判断所有节点是否拥有相同版本的 Ring。Ring 文件的维护是管理员的责任。当然,这些任务可以通过 Swift 自身外部的机制来自动化。

Ring 允许任何 Swift 服务识别哪个存储节点用于查询特定存储实体。Ring.get_nodes(account, container=None, obj=None) 方法用于识别给定路径(/account[/container[/object]])的目标存储节点。它返回一个包含分区和节点字典的元组。分区用于构造对象文件或账户/容器数据库的本地路径。节点字典的元素结构与设备列表中的设备相同(参见上文)。

Ring 管理

Swift 服务无法更改 Ring。Ring 由 swift-ring-builder 脚本管理。创建新 Ring 时,管理员首先应指定构建器文件和 Ring 的主要参数:分区幂(或分区移位值)、集群中每个分区的副本数,以及在特定分区可以连续移动之前的时间(以小时为单位)。

swift-ring-builder <builder_file> create <part_power> <replicas> <min_part_hours>

创建临时构建器文件结构后,管理员应向 Ring 添加设备。对于每个设备,所需的参数是区域编号、存储节点的 IP 地址、服务器监听的端口、设备名称(例如sdb1)、可选的设备元数据(例如,型号名称、安装日期或任何其他信息)和设备权重。

swift-ring-builder <builder_file> add z<zone>-<ip>:<port>/<device_name>_<meta> <weight>

设备权重用于在设备之间分配分区。设备权重越大,分配给该设备的就越多。建议的初始方法是使用集群中相同大小的设备,并将每个设备的权重设置为 100.0。对于稍后添加的设备,权重应与容量成比例。此时,集群中所有初始设备都应添加到 Ring 中。创建实际 Ring 文件之前,可以验证构建器文件的一致性。

swift-ring-builder <builder_file>

如果验证成功,下一步是将分区分配给设备并创建实际的 Ring 文件。这称为“重新平衡”Ring。此过程旨在尽量减少移动的分区数,以最大限度地减少节点之间的数据交换,因此在重新平衡 Ring 之前完成所有必要的 Ring 更改非常重要。

swift-ring-builder <builder_file> rebalance

整个过程必须为所有三个 Ring:账户、容器和对象重复。生成的.ring.gz文件应推送到集群中的所有节点。构建器文件也需要用于 Ring 的未来更改,因此应进行备份并保存在安全的地方。一种方法是将它们作为普通对象存放到 Swift 存储中。

物理磁盘使用

分区本质上是存储在集群中的数据块。但这并不意味着所有分区的磁盘使用量都是恒定的。对象在分区之间的分配基于对象路径哈希,而不是对象大小或其他参数。对象不是分区的,这意味着一个对象在存储节点文件系统中作为单个文件存储(除了非常大的对象,大于 5GB,它们可以分段上传 - 请参阅 Swift 文档)。

映射到存储设备的那个分区实际上是/srv/node/<dev_name>下的一个目录。该目录使用的磁盘空间可能因分区而异,具体取决于通过对象路径哈希到 Ring 的映射将哪些大小的对象放入该分区。

总而言之,Swift Ring 是一个优美的结构,尽管它在自动化和节点之间的同步方面还有待提高。我将在接下来的文章中讨论如何解决这些问题。

更多信息

有关 Swift Ring 的更多信息可以在以下来源找到:
官方 Swift 文档 – 数据结构描述的基础来源
Github 上的 Swift Ring 源代码RingRingBuilderSwift 类
Chmouel Boudjnah 的博客 – 包含有用的 Swift 技巧

标签:

留下回复

您的电子邮件地址将不会被公开。 必填字段已标记 *