这是总结我们对 Swift 架构分析的系列文章的第一篇。我们试图突出官方文档中不够清晰的一些要点。我们的主要基础是对源代码的深入研究。
以下材料适用于 Swift 1.4.6 版本。
The Ring 是 Swift 架构中至关重要的部分。这个一半数据库、一半配置文件的结构跟踪集群中所有数据的位置。对于集群中任何存储实体到特定设备的任何可能路径,The Ring 都指向特定节点上的特定设备。
Swift 识别三种类型的实体:帐户、容器和对象。每种类型都有自己的 ring,但所有三个 ring 都是以相同的方式构建的。Swift 服务使用相同的源代码来创建和查询所有三个 ring。Swift 的两个类负责这些任务RingBuilder和Ring分别。
Ring 数据结构
Swift 中的所有三个 Ring 都是由 3 个元素组成的结构
- 集群中的设备列表,也称为设备在Ring类中;
- 指示分区到数据分配的设备 ID 列表的列表,存储在名为_replica2part2dev_id;
- 的变量中。一个整数,用于对哈希路径(帐户/容器/对象)进行 MD5 哈希运算,以计算哈希的分区索引(分区偏移值,part_shift).
设备列表
设备列表包含环已知的所有存储设备(磁盘)。此列表的每个元素都是以下结构的字典
| 键 |
类型 |
值 |
| id |
整数 |
设备列表的索引 |
| zone |
整数 |
设备所在的区域 |
| weight |
浮点数 |
设备相对于环中其他设备的相对权重 |
| ip |
字符串 |
包含设备的服务器的 IP 地址 |
| port |
整数 |
服务器用于为设备提供请求的 TCP 端口 |
| device |
字符串 |
主机系统中设备的磁盘名称,例如sda1。它用于标识主机系统下的磁盘挂载点/srv/node。 |
| meta |
字符串 |
用于存储有关设备的任意信息的通用字段。服务器不直接使用 |
可以使用列表中的值执行一些设备管理。首先,对于已删除的设备,'id'值设置为'None'。设备 ID 通常不会被重用。其次,将'weight'设置为 0.0 会暂时禁用该设备,因为不会将任何分区分配给该设备。
分区分配列表
此数据结构是 N 个元素的列表,其中 N 是集群的副本数。默认副本数为 3。分区分配列表的每个元素都是一个array('H'),或 Python 紧凑高效的无符号短整数数组。这些值实际上是对设备列表的索引(参见上一节)。因此,每个array('H')在分区分配列表中表示将分区映射到设备 ID。
环从路径的 MD5 哈希中获取可配置数量的位,并将其转换为长整数。该数字用作array('H')的索引。该索引指向指定映射到该设备的分配的分区的数组元素。从哈希保留的位数称为分区幂,而 2 的分区幂指示分区数。
对于给定的分区号,每个副本的设备将不会与任何其他副本的设备位于同一区域。可以使用区域根据物理位置、电源隔离、网络隔离或任何其他可能导致多个副本同时不可用的属性对设备进行分组。
分区偏移值
这是从 MD5 哈希中获取的位数'/account/[container/[object]]'路径,用于计算路径的分区索引。分区索引是通过将哈希的二进制部分转换为整数来计算的。
环操作
上述结构存储为腌制(参见 Python pickle)和 gzip 压缩(参见 Python gzip.GzipFile)文件。有三个文件,每个环一个,通常它们的名称是
account.ring.gzcontainer.ring.gzobject.ring.gz
这些文件必须存在于/etc/swiftSwift 集群中的每个节点(代理和存储)上的目录中,因为这些节点上的服务使用它来定位集群中的实体。此外,集群中所有节点上的环文件必须具有相同的内容,以便集群可以正常运行。
Swift 中没有内部机制可以保证环的一致性,即 gzip 文件未损坏且可读取。Swift 服务无法确定所有节点是否具有相同版本的环。维护环文件是管理员的责任。当然,这些任务可以通过 Swift 外部的手段自动化。
环允许任何 Swift 服务识别查询特定存储实体应该查询哪个存储节点。使用方法 Ring.get_nodes(account, container=None, obj=None) 用于识别给定路径(/account[/container[/object]])的目标存储节点。它返回分区和节点字典的元组。分区用于构造对象文件或帐户/容器数据库的本地路径。节点字典元素与设备列表中的设备具有相同的结构(参见上方)。
环管理
Swift 服务无法更改环。环由 swift-ring-builder 脚本管理。创建新环时,管理员应首先指定生成器文件和环的主要参数:分区幂(或分区偏移值)、集群中每个分区的副本数以及特定分区可以在连续时间内移动之前的小时数
swift-ring-builder <builder_file> create <part_power> <replicas> <min_part_hours>
创建临时生成器文件结构后,管理员应将设备添加到环中。对于每个设备,所需值是区域编号、存储节点的 IP 地址、服务器侦听的端口、设备名称(例如sdb1)、可选的设备元数据(例如,型号名称、安装日期或任何其他内容)和设备权重
swift-ring-builder <builder_file> add z<zone>-<ip>:<port>/<device_name>_<meta> <weight>
设备权重用于在设备之间分配分区。设备权重越高,分配给该设备的的分区就越多。建议的初始方法是在整个集群中使用相同大小的设备,并将权重设置为每个设备 100.0。对于以后添加的设备,权重应与容量成比例。此时,应将所有最初将在集群中使用的设备添加到环中。在创建实际环文件之前,可以验证生成器文件的一致性
swift-ring-builder <builder_file>
如果验证成功,下一步是在设备之间分配分区并创建实际环文件。这称为“重新平衡”环。此过程旨在尽可能地移动较少的分区,以最大限度地减少节点之间的数据交换,因此在重新平衡之前进行所有必要的环更改非常重要
swift-ring-builder <builder_file> rebalance
必须对所有三个环(帐户、容器和对象)重复整个过程。生成的.ring.gz文件应推送到集群中的所有节点。生成器文件也需要用于将来更改环,因此应备份并保存在安全的地方。一种方法是将它们作为普通对象放入 Swift 存储中。
物理磁盘使用情况
分区本质上是集群中存储的数据块。但是,这并不意味着所有分区的磁盘使用量都是恒定的。对象在分区之间的分布基于对象路径哈希,而不是对象大小或其他参数。对象不被分区,这意味着对象作为存储节点文件系统中的单个文件存储(除了大于 5Gb 的非常大的对象,这些对象可以分段上传 – 参见 Swift 文档)。
映射到存储设备的分配实际上是结构中的一个目录/srv/node/<dev_name>。此目录使用的磁盘空间可能因放置在该分区中的对象的大小而异,方法是根据对象路径的哈希映射到环。
总而言之,Swift 环是一个美丽的结构,但它缺乏一定程度的自动化和节点之间的同步。我将在后续帖子中介绍如何解决这些问题。
更多信息
有关 Swift 环的更多信息可以在以下来源找到
官方 Swift 文档 – 描述数据结构的基准来源
Swift 环 Github 源代码 –Ring和RingBuilderSwift 类的代码库。
Chmouel Boudjnah 的博客 – 包含有用的 Swift 提示