复制

由于 Swift 中的每个副本独立运行,并且客户端通常只需要简单多数节点响应才能认为操作成功,因此瞬态故障(如网络分区)可能很快导致副本发散。这些差异最终由异步的、点对点复制器进程协调。复制器进程遍历其本地文件系统,并发执行操作,从而在物理磁盘上平衡负载。

复制使用推送模型,记录和文件通常仅从本地复制到远程副本。这一点很重要,因为节点上的数据可能不属于那里(例如,在交接和环变更的情况下),并且复制器无法知道集群中其他位置存在哪些数据应该拉取。确保数据到达其应在的位置是任何包含数据的节点的职责。副本放置由环处理。

系统中删除的每个记录或文件都标记为墓碑,以便可以与创建一起复制删除操作。复制过程在称为一致性窗口的时间段后清理墓碑。一致性窗口包括复制持续时间和瞬态故障可能使节点从集群中移除的时间。为了达到副本收敛,墓碑清理必须与复制相关联。

如果复制器检测到远程驱动器已失败,复制器将使用环的 get_more_nodes 接口选择另一个要同步的节点。复制器可以在磁盘故障的情况下维持期望的复制级别,尽管某些副本可能不在立即可用的位置。请注意,复制器在其他故障(如整个节点故障)发生时不会维持期望的复制级别,因为大多数故障是瞬态的。

复制是一个积极开发中的领域,并且可能存在许多潜在的改进,以提高速度和正确性。

有两种主要的复制器类型——数据库复制器,它复制帐户和容器,以及对象复制器,它复制对象数据。

数据库复制

数据库复制执行的第一步是低成本的哈希比较,以确定两个副本是否已经匹配。在正常操作下,此检查能够快速验证系统中的大多数数据库是否已经同步。如果哈希不同,复制器通过共享自上次同步点以来添加的记录,将数据库同步。

同步点是一个高水位线,指示两个数据库已知同步的最后一个记录,并存储在每个数据库中,作为远程数据库 ID 和记录 ID 的元组。数据库 ID 在数据库的所有副本中是唯一的,记录 ID 是单调递增的整数。在将所有新记录推送到远程数据库后,将本地数据库的整个同步表推送到远程数据库,以便远程数据库可以保证它与本地数据库之前同步的所有内容同步。

如果发现副本完全丢失,则使用 rsync(1) 将整个本地数据库文件传输到对等节点,并赋予一个新的唯一 ID。

在实践中,数据库复制可以处理每并发设置每秒数百个数据库(最多可用 CPU 或磁盘的数量),并且受必须执行的数据库事务数量的限制。

对象复制

对象复制的初始实现只是执行 rsync 将数据从本地分区推送到所有预期存在的远程服务器。虽然这在小规模上表现良好,但一旦目录结构无法保存在 RAM 中,复制时间就会急剧增加。现在我们使用这种方案的修改版本,其中保存每个后缀目录的内容的哈希值到每个分区的哈希文件。当后缀目录的内容被修改时,该后缀目录的哈希值将被使无效。

对象复制过程读取这些哈希文件,计算任何无效的哈希值。然后,它将哈希值传输到应该保存分区的每个远程服务器,并且只有在远程服务器上哈希值不同的后缀目录才会被 rsync。在将文件推送到远程服务器后,复制过程会通知它重新计算 rsync 后缀目录的哈希值。

对象复制的性能通常受其必须遍历的未缓存目录数量的限制,通常是由于无效的后缀目录哈希值造成的。使用我们正在运行的系统中的写入量和分区计数,它的设计目的是每天大约 2% 的节点上的哈希空间将被无效,这在实验中为我们提供了可接受的复制速度。

目前正在进行使用新的 ssync 方法的工作,其中根本不使用 rsync,而是使用完全 Swift 代码来传输对象。首先,此 ssync 将努力模拟 rsync 行为。一旦被认为稳定,它将为未来的复制改进铺平道路,因为我们将能够轻松地在复制路径中添加代码,而不是试图修改 rsync 代码库并分发这些修改。

计划的第一个改进是“index.db”,它将取代 hashes.pkl。这将允许更快地更新这些数据以及更精简的查询。我们很可能会实现比当前 hashes.pkl 使用的方案更好的方案(哈希树,等等)。

一路计划的另一个改进是将本地磁盘结构与协议路径结构分离。这种分离将允许在某个时候进行环调整大小,或者至少进行环加倍。

请注意,对于使用 Erasure Code 策略存储的对象,复制器守护程序不参与。相反,Erasure Code 策略使用重建器,并且类似于复制类型的策略的复制器。有关 Erasure Code 支持以及重建器的完整信息,请参阅 Erasure Code 支持

Hashes.pkl

hashes.pkl 文件是复制和重建(用于 Erasure Coding)的关键元素。两个守护程序都使用此文件来确定参与耐久性方案的节点之间是否需要采取任何操作。该文件本身是一个腌制的字典,具体格式取决于策略是复制还是 Erasure Code。无论哪种情况,节点之间都提供相同的基础信息。该字典包含一个字典,其中键是后缀目录名称,值是该后缀目录的目录列表的 MD5 哈希值。通过这种方式,守护程序可以快速识别分区范围内本地和远程后缀目录之间的差异,因为一个 hashes.pkl 文件的范围是一个分区目录。

对于 Erasure Code 策略,需要更多信息。对象的哈希目录可能包含单个对象的多个片段,如果节点充当交接或正在进行重新平衡,则如此。对象的每个片段都存储在一个片段索引中,因此 Erasure Code 分区的 hashes.pkl 仍然是一个以后缀目录名称为键的字典,但是该值是另一个以片段索引为键的字典,后续 MD5 哈希值作为值。对象哈希目录中的某些文件不需要片段索引,因此使用 None 来表示这些文件。以下是这些字典可能的样子示例。

复制 hashes.pkl

{'a43': '72018c5fbfae934e1f56069ad4425627',
 'b23': '12348c5fbfae934e1f56069ad4421234'}

Erasure Code hashes.pkl

{'a43': {None: '72018c5fbfae934e1f56069ad4425627',
         2: 'b6dd6db937cb8748f50a5b6e4bc3b808'},
 'b23': {None: '12348c5fbfae934e1f56069ad4421234',
         1: '45676db937cb8748f50a5b6e4bc34567'}}

专用复制网络

Swift 支持使用专用网络进行复制流量。有关更多信息,请参阅 专用复制网络概述