跳转到: 导航, 搜索

已废弃:Swift-Improved-Object-Replicator

摘要

本蓝图的目标是提高对象复制器的效率。在Swift中,对象复制器在保持数据一致性和数据持久性方面发挥着重要作用。但是,对于数十亿对象的生产环境,由于同步数据阶段耗时,对象复制器的性能并不理想。此外,在发生故障期间,同步数据的时间会影响数据持久性和数据一致性。因此,优化对象复制器的机制是必要的。

本蓝图讨论了对象复制器的当前实现和性能问题,以及如何在新的对象复制器中解决这些问题。

当前对象复制器的实现

作为一个类似 Dynamo 的存储系统,复制器的主要任务如下

副本同步:一种反熵协议,用于保持副本的一致性和副本恢复。提示切换恢复:为了确保由于临时节点或网络故障而不会导致读写操作失败,当原始节点不可用时,临时节点会成为切换节点。因此,在原始节点可用后,副本应从切换节点移动到原始节点。

在 Swift 中,对象复制器负责副本同步、提示切换恢复和目录回收。对象复制器的主要逻辑如下所示

01: partitions = collect_partitions()
02: # collect jobs
03: for partition in partitions:
04:     if partition not in local:
05:         partitions_to_move.add(partition)
06:     else:
07:         partitions_to_check.add(partition)
08: # hinted handoff recovery
09: for partition in partitions_to_move:
10:     move_to_origin_node(partiotion)
11: # replica synchronization
12: for partition in partitions_to_check:
13:     local_hash_of_suffix_dirs = get_hashes_and_reclaim(partition)
14:     remote_hash_of_suffix_dirs = get_reomote_hashes(partition)
15:     different_suffix_dirs = compare(local_hash_of_suffix_dirs, remote_hash_of suffix_dirs)
16:     rsync(different_suffix_dirs)

当前对象复制器的不足

一轮复制耗时过长

对于我的 Swift 部署,一台服务器上有大约 150,000 个分区和 30,000,000 个后缀目录。磁盘使用率百分比为 12%(约 2T)。在一般情况下,一轮复制需要 15 小时。如果有节点故障,则需要更多时间。随着对象数量的增加,复制时间会越来越长。对于数据一致性和持久性来说,复制时间过长是不可接受的。即使我们可以添加更多节点来缩短时间,我们也会考虑投资回报率。

对象复制器灵活性不足

一轮复制会循环磁盘上的所有分区并随机同步它们。因此,如果有一些紧急副本需要同步,当前机制无法尽快同步它们。

设计原则

  1. 将同步过程拆分为异步;
  2. 实时计算复制的哈希值;
  3. 与当前对象复制器兼容;
  4. 复制过程可控;

实现

设计将分为两部分描述。首先,一个图显示了整体架构;其次,将详细讨论该架构的组件。

Swift-new-object-replicator-architecture.png

如图所示,带有颜色的图标是此设计的主要组件。它们都与当前对象复制器独立。对象复制器的原始逻辑被拆分为四个部分,分别用不同的颜色表示。颜色为青色的组件负责实时计算哈希值;颜色为粉色的组件负责索引后缀和分区目录的哈希值,接收和发送请求以比较分区或后缀的哈希值,生成将后缀目录复制到复制队列的作业;分区监视器负责以间隔检查分区是否需要移动;后缀传输器负责监视复制队列并调用 rsync 来同步后缀目录。

实时计算哈希值

update-monitor

更新监视器负责监视对象服务器和 rsync 服务器的日志文件。PUT 和 DELETE 操作的日志将被 update-monitor 过滤并放入 hash-update-queue 中。

hash-update-queue

一个用于存储 update-monitor 数据的队列。

hash-updater

一个守护进程,用于计算 hash-update-queue 中的后缀目录的哈希值并将其更新到哈希表中。

== 哈希表 == 哈希表是一种键值存储,用于存储一个磁盘上的 {partition/suffix: hash} 和 {partition: hash} 项目。{partition/suffix: hash} 与分区目录下的 hashes.pkl 文件相同。{partition: hash} 是所有 {partition/suffix: hash} 的 MD5 校验和。结构如下所示。

Swift-hash-table.png

哈希表中的项目总数将达到一亿。我们可以使用以下公式计算近似的项目数。

item_num ~= (partition_num * replica_num * suffix_num + partition_num * replica_num) / disk_num;<
> item_num ~= partition_num * replica_num * (1 + suffix_num) / disk_num<
> 因为最大后缀数 = 16^3 = 4096,所以:<
> item_num ~= partition_num * replica_num * 4097 / disk_num<
>

在本公式中,我们认为磁盘的权重都相同。item_num 可能很大或很小。它取决于 partition_num、replica_num 和 disk_num 的配置。例如,如果分区数为 218,replica_num 为 3 且 disk_num 为 50,则 item_num ~= 218 * 3 * 4097 / 50 ~= 64440238。

考虑到哈希表的大小,我们不能将所有哈希表存储在内存中。我们必须将哈希表存储在磁盘上并在内存中缓存部分数据。因此,我们使用 LSM-Tree 和 bloom 过滤器来实现此目标。LSM-Tree,Log-Structured Merge-Tree,是一种基于磁盘的数据结构,旨在为经历长时间高记录插入(和删除)速率的文件提供低成本索引。Bloom 过滤器是一种空间高效的概率数据结构,用于测试一个元素是否是集合的成员。LSM-Tree 和 bloom 过滤器的集成可以提供高性能的写入和读取操作。有一些开源项目实现了 LSM-Tree 和 bloom 过滤器,例如 leveldb 和 nessDB。我们可以使用其中一个来存储哈希表。

Replication-Server

Replication-server 是一个 REST-API 服务器,用于接收来自对等节点或控制节点的请求。来自对等节点的请求是比较一个分区的哈希值,如果本地分区的哈希值与远程哈希值不同,则 replication-server 将返回所有后缀目录的哈希值作为响应。来自控制节点的请求是同步特定的分区或后缀目录。因此,replication-server 应该处理这种类型的请求并生成作业到 replication-queue。例如,在重新平衡环之后,有一些分区需要传输,传输一个分区可以拆分为不同的节点执行的多个后缀传输部分。

Partition-Monitor

Partition-monitor 是一个以间隔运行的守护进程。Partition-monitor 的主要任务如下所示

  • 收集需要移动的分区并将它们放入 replication-queue。

Partition-monitor 将磁盘上的所有分区与环进行比较。如果分区是 handoff 或需要传输,partition-monitor 将将其添加到 replication-queue。

  • 向对等节点的 replication-server 发送请求以比较哈希值,如果它们不同,则将其放入 replication-queue。

这里可以使用优先级队列。Partition-monitor 首先比较最活跃的分区。

Suffix-Transporter

一个守护进程,用于执行 replication-queue 中的作业。

总结

本蓝图的原则是将对象复制器的同步过程拆分为异步。通过进行此更改,复制可以更加灵活和可控。特定的 replication-server 可以拆分来自对象服务器的 REPLICATE 请求并接收来自控制节点的请求。可以在 hash-update-queue 和 replication-queue 中使用一些策略来控制复制。所有这些都基于 Dynamo 的论文并与对象复制器兼容。