Erasure Code 支持¶
历史和工作原理¶
关于 Erasure Code (EC) 理论有很多优秀的资料,本简短介绍旨在提供一些基本背景,帮助读者更好地理解 Swift 中的实现。
Erasure Coding 用于存储应用,早在 1960 年代就随着 Reed-Solomon 码从编码理论中发展而来。这些码多年来一直被用于各种应用,从 CD 到 DVD,再到通用通信,甚至包括从 Voyager 开始的太空计划!基本思想是将一定量的数据分解成较小的片段,并以可以容忍丢失一些编码片段的方式进行编码。这就是“擦除”一词的由来,如果你传输 14 个片段,而只收到 13 个,那么其中一个就被认为是“擦除”的。 “擦除”一词为 EC 提供了重要的区别;它不是关于检测错误,而是关于处理故障。EC 的另一个重要元素是可以调整可以容忍的擦除数量,以满足应用程序的需求。
从高层来看,EC 通过使用特定方案将单个数据缓冲区分解成几个较小的数据缓冲区,然后,根据方案,对该数据执行一些编码操作以生成额外的信息。因此,你最终得到的数据比开始时多,而这些额外的数据通常被称为“奇偶校验”。请注意,有许多、许多不同的编码技术,它们在组织和操作数据的方式以及用于计算奇偶校验的方式上都有所不同。例如,一种方案可能依赖于 Galois 域算术,而其他方案可能只使用 XOR。这些变体和它们之间的差异的细节超出了本介绍的范围,但我们将会在 Swift 中 EC 的实现中更多地讨论其中的一些。
Swift 中 EC 支持的概述¶
首先,从应用程序的角度来看,EC 支持是完全透明的。没有与 EC 相关的外部 API;容器只是使用定义为使用 EC 的存储策略创建,然后与集群的交互与任何其他持久性策略相同。
EC 在 Swift 中作为存储策略实现,请参阅 存储策略 以获取有关存储策略的完整详细信息。由于支持作为存储策略实现,因此可以隔离与集群的 EC 功能相关的所有存储设备。完全有可能在存储策略之间共享设备,但对于 EC,可能更有意义的是不仅使用单独的设备,甚至可能使用专门用于 EC 的整个节点。
选择哪个方向取决于部署 EC 策略的原因。例如,如果已经存在生产复制策略,并且目标是添加一个冷存储层,以便尽可能减少执行复制的现有节点的影响,那么添加一组专门用于 EC 的新节点可能最有意义,但成本也最高。另一方面,如果 EC 被添加为提供特定应用程序的额外持久性的功能,并且现有基础设施适合 EC(足够的节点,EC 所选方案的区域),那么利用现有基础设施,使 EC 环与复制环共享节点最有意义。这些是主要考虑因素
现有基础设施的布局。
添加专用 EC 节点(或仅添加专用 EC 设备)的成本。
预期使用模型。
Swift 代码库不包含执行数据实际编码和解码所需的任何算法;这留给外部库。存储策略架构被利用起来,以便基于每个容器启用 EC——对象环仍然用于确定 EC 数据片段的放置。虽然有几条代码路径是与 EC 策略相关的操作所独有的,但 Swift 依赖于外部 Erasure Code 库来执行低级 EC 函数。使用外部库可以实现最大的灵活性,因为那里有大量的选项,每个选项都有其自身的优缺点,这些优缺点因用例而异。
PyECLib:外部 Erasure Code 库¶
PyECLib 是一个 Python Erasure Coding 库,最初是作为为 Swift 项目添加 EC 支持而设计和编写的,但它是一个独立的项目。该库提供了一个定义明确且简单的 Python 接口,并在内部实现了一个插件架构,使其能够利用许多众所周知的 C 库,例如
Jerasure 和 GFComplete 在 http://jerasure.org。
Intel(R) ISA-L 在 http://01.org/intel%C2%AE-storage-acceleration-library-open-source-version。
或者编写你自己的!
PyECLib 使用一个名为 liberasurecode 的 C 基于库来实现插件基础设施;liberasurecode 可在
liberasurecode:https://github.com/openstack/liberasurecode
因此,PyECLib 本身允许不仅选择,而且进一步扩展。PyECLib 还附带一个方便的实用程序,可帮助确定基于将要使用的设备(处理器和服务器配置在每种算法的性能方面可能有所不同)的最佳算法。将在配置部分对此进行更多介绍。PyECLib 作为 Swift 的要求包含在内。
有关完整详细信息,请参阅 PyECLib
存储和检索对象¶
我们将在后面的“幕后”部分讨论 PUT 和 GET 的工作细节。这里的关键点是,所有擦除码工作都在后台进行;本摘要仅为高级信息概述。
PUT 流程如下
代理服务器流式传输一个对象并缓冲“一段”数据(大小可配置)。
代理服务器调用 PyECLib 将数据编码成较小的片段。
代理服务器根据环位置将编码片段流式传输到存储节点。
重复,直到客户端完成发送数据。
当达到法定数量时,会通知客户端完成。
GET 流程如下
代理服务器同时向参与节点发出请求。
一旦代理服务器拥有所需的片段,它就会调用 PyECLib 来解码数据。
代理服务器将解码后的数据流式传输回客户端。
重复,直到代理服务器完成将数据发送回客户端。
从这个高级概述来看,使用 EC 似乎会导致每个节点本地文件系统中实际文件数量的爆炸。虽然存储的文件更多(因为对象被分解成片段)是事实,但实现尽可能地减少了这一点,更多详细信息可在“幕后”部分中找到。
交接节点¶
在 EC 策略中,与复制类似,交接节点是一组存储节点,用于增强负责存储擦除编码对象的原始节点列表。如果一个或多个原始节点不可用,则使用这些交接节点。交接节点仍然是根据尝试实现数据放置的最大分离来选择的。
重建¶
对于 EC 策略,重建类似于复制类型策略的复制过程——本质上,“重建器”取代了 EC 策略类型的“复制器”。重建的基本框架与复制非常相似,但有一些值得注意的例外
由于 EC 实际上不复制分区,因此它需要在比 rsync 提供的更细的粒度上运行,因此 EC 利用了幕后的大部分 ssync(你不需要手动配置 ssync)。
一旦一对节点确定需要替换丢失的对象片段,而不是像复制那样推送副本,重建器必须从其他节点读取足够的幸存片段并执行本地重建,然后才能将正确的数据推送到另一个节点。
重建器不会与负责 EC 分区的节点集中的所有其他重建器通信,这将过于冗长,而是每个重建器负责与分区的最接近的两个邻居同步(最接近意味着环上的左侧和右侧)。
注意
EC 工作(编码和解码)发生在代理节点上,用于 PUT/GET 操作,以及存储节点上用于重建。与复制一样,重建可能是重新平衡、位腐烂、驱动器故障或将数据从交接节点恢复到其原始节点的结果。
性能考虑因素¶
通常,EC 具有与复制数据不同的性能特征。EC 需要大量的 CPU 来读写数据,更适合于不经常访问的大对象(例如备份)。
鼓励操作员表征各种 EC 方案的性能并与开发社区分享他们的观察结果。
使用 Erasure Code 策略¶
要使用 EC 策略,管理员只需在 swift.conf 中定义一个 EC 策略并创建/配置关联的对象环即可。以下显示了如何设置 EC 策略的示例
[storage-policy:2]
name = ec104
policy_type = erasure_coding
ec_type = liberasurecode_rs_vand
ec_num_data_fragments = 10
ec_num_parity_fragments = 4
ec_object_segment_size = 1048576
让我们更详细地了解每个配置参数
name:这是一个标准的存储策略参数。有关详细信息,请参阅 存储策略。policy_type:将其设置为erasure_coding以指示这是一个 EC 策略。ec_type:根据所选 PyECLib 后端中的可用选项设置此值。这指定了要使用的 EC 方案。例如,此处显示的选项选择 Vandermonde Reed-Solomon 编码,而flat_xor_hd_3选项将选择基于 Flat-XOR 的 HD 组合码。有关完整详细信息,请参阅 PyECLib 页面。ec_num_data_fragments:将由数据组成的片段总数。ec_num_parity_fragments:将由奇偶校验组成的片段总数。ec_object_segment_size:在将一段馈送到编码器/解码器之前,将缓冲的数据量。默认值为 1048576。
当 PyECLib 编码一个对象时,它会将对象分解成 N 个片段。但是,在配置期间重要的是有多少是数据,有多少是奇偶校验。因此,在上面的示例中,PyECLib 实际上会将一个对象分解成 14 个不同的片段,其中 10 个由实际对象数据组成,4 个由奇偶校验数据组成(具体取决于 ec_type)。
在决定 EC 策略的对象环中使用哪些设备时,请务必仔细考虑性能影响。在部署之前,强烈建议在测试环境中对您的配置进行一些性能基准测试。
要创建 EC 策略的对象环,swift-ring-builder create 命令的用法中唯一的区别是 replicas 参数。replicas 值是跨与环关联的对象服务器传播的片段数;replicas 必须等于 ec_num_data_fragments 和 ec_num_parity_fragments 的总和。例如
swift-ring-builder object-1.builder create 10 14 1
请注意,在此示例中,replicas 值 14 基于 10 个 EC 数据片段和 4 个 EC 奇偶校验片段的总和。
配置完 swift.conf 中的 EC 策略并创建对象环后,您的应用程序只需使用指定的策略名称创建容器并像往常一样进行交互即可开始使用 EC。
注意
重要的是要注意,一旦部署了策略并使用该策略创建了对象,就无法更改这些配置选项。如果需要更改配置,则必须创建新策略并将数据迁移到新容器。
警告
使用具有 4 个以上奇偶校验片段的 isa_l_rs_vand 会创建在某些情况下可能无法正确重建或(使用 liberasurecode < 1.3.1)重建损坏数据的片段。需要大量奇偶校验片段的新策略应考虑使用 isa_l_rs_cauchy。任何受影响的现有策略都必须标记为已弃用,并且该策略中的数据应迁移到新策略。
策略之间的迁移¶
EC 的常见用途是从更昂贵但延迟较低的策略(例如复制)迁移访问较少的数据。当应用程序确定要将数据从复制策略移动到 EC 策略时,它只需将数据从复制容器移动到使用目标持久性策略创建的 EC 容器。
全局 EC¶
当部署跨 全局集群 的 EC 策略时,建议如下
代理服务器应 配置为使用读取亲和性 以优先从其本地区域读取全局 EC 策略。 每策略配置 允许为单个策略配置此设置。
注意
在部署全局 EC 策略之前,应考虑 已知问题,特别是对象重建器的预期性能较差。
EC 复制¶
EC 复制使 Swift 能够复制擦除编码对象的片段。如果 EC 存储策略配置了非默认的 ec_duplication_factor 为 N > 1,则该策略将为从配置的 EC 引擎返回的每个唯一片段创建 N 个副本。
EC 片段的复制对于 Global EC 存储策略来说是最佳的,这些策略需要跨故障域分散片段数据。如果没有片段复制,常见的 EC 参数将无法在大型故障域之间分配足够的唯一片段,从而允许使用来自任何一个域的片段进行重建。例如,均匀分布的 10+4 EC 策略模式会将 7 个片段放置在两个故障域中的每一个,这少于重建丢失片段所需的 10 个片段。
如果没有片段复制,EC 策略模式必须调整为包含额外的奇偶校验片段,以保证每个故障域中的片段数量大于重建所需的数量。例如,均匀分布的 10+18 EC 策略模式会将 14 个片段放置在两个故障域中的每一个,这足以在每个故障域中重建丢失的片段。然而,经验测试表明,编码具有 num_parity > num_data (例如 10+18) 的模式不如使用片段复制效率高。EC 片段复制使 Swift 的 Global EC 能够在不牺牲读/写或重建效率的情况下,在故障域之间保持更多的独立性!
可以在每个 storage-policy 部分的 swift.conf 中配置 ec_duplication_factor 选项。可以省略该选项 - 默认值为 1 (即,不复制)。
[storage-policy:2]
name = ec104
policy_type = erasure_coding
ec_type = liberasurecode_rs_vand
ec_num_data_fragments = 10
ec_num_parity_fragments = 4
ec_object_segment_size = 1048576
ec_duplication_factor = 2
警告
EC 复制旨在与 Global EC 策略一起使用。为了确保在所有区域中数据的独立可用性,ec_duplication_factor 选项应仅与 复合环 结合使用,如本文档所述。
在此示例中,10+4 模式和复制因子 2 将导致存储 (10+4)x2 = 28 个片段(我们将使用简写 10+4x2 来表示该策略配置)。该策略的环应配置为 28 个副本(即 (ec_num_data_fragments + ec_num_parity_fragments) * ec_duplication_factor)。10+4x2 模式 可以 允许跨区域部署在即使 超过 14 个片段不可用时,将对象重建到完全的持久性。这在 10+18 配置方面具有优势,不仅因为从数据片段读取会更常见和更有效,而且因为 10+4x2 可以扩展到 10+4x3 以扩展到另一个区域。
EC 复制与复合环¶
建议将 EC 复制与 复合环 结合使用,以便将重复片段分散到各个区域。
当使用 EC 复制时,强烈希望每个区域放置每个片段的一个副本。这确保了可以始终从单个区域组装一组 ec_num_data_fragments 个唯一片段(重建对象所需的最小数量)。反过来,这意味着对象在整个区域不可用时具有鲁棒性。
可以通过使用具有以下属性的 复合环 来实现这一点
复合环中组件环的数量等于策略的
ec_duplication_factor。每个组件环具有与
ec_num_data_fragments和ec_num_parity_fragments之和相等的replicas数量。每个组件环填充了唯一区域中的设备。
这种安排导致复合环中的每个组件环,因此每个区域,都具有每个片段的一个副本。
例如,考虑一个具有两个区域 region1 和 region2 以及 4+2x2 EC 策略模式的 Swift 集群。该策略应使用具有两个组件环 ring1 和 ring2 的复合环,这些环专门位于区域 region1 和 region2 中。每个组件环应具有 replicas = 6。结果是,对象的第一个 6 个片段始终放置在 ring1 (即,在 region1 中),第二个 6 个重复片段始终放置在 ring2 (即,在 region2 中)。
相反,跨两个区域的传统环可能会导致重复片段在区域中的分布不佳;有可能将相同片段的副本放置在同一区域,从而导致另一个区域没有该片段的副本。这可能会使从单个区域组装一组 ec_num_data_fragments 个唯一片段变得不可能。例如,传统环可能具有病态的次优放置,例如
r1
<timestamp>#0#d.data
<timestamp>#0#d.data
<timestamp>#2#d.data
<timestamp>#2#d.data
<timestamp>#4#d.data
<timestamp>#4#d.data
r2
<timestamp>#1#d.data
<timestamp>#1#d.data
<timestamp>#3#d.data
<timestamp>#3#d.data
<timestamp>#5#d.data
<timestamp>#5#d.data
在这种情况下,无法从单个区域重建对象;region1 仅具有索引为 0, 2, 4 的片段,而 region2 具有其他 3 个索引,但我们需要 4 个唯一索引才能重建对象。
读取的节点选择策略¶
代理服务器在处理 EC 策略的 GET 请求时,需要一组唯一的片段索引来解码原始对象。对于传统的 EC 策略,这很可能是从随机选择的后端节点读取片段的结果。对于 EC 复制策略,从随机选择的后端节点收到的响应包含一些重复片段的可能性要大得多。
因此,强烈建议始终将 EC 复制与 复合环 和 代理服务器读取亲和性 结合使用。
在推荐的部署下,在正常情况下,读取亲和性将导致代理服务器首先尝试从其本地区域的节点读取片段。这些片段彼此之间保证是唯一的。即使发生少量本地故障,唯一的本地奇偶校验片段也会弥补差异。但是,如果足够多的本地主存储节点发生故障,以至于本地区域中没有足够的唯一片段可用,则全局 EC 集群将继续从其他区域读取片段。从远程区域的随机读取不能保证返回唯一片段;使用 EC 复制,代理服务器遇到重复片段的可能性很高。代理服务器将忽略这些片段并发出其他请求,直到它积累所需的唯一片段集合,可能在本地和远程区域的全部主节点和交接位置进行搜索,然后最终使读取失败。
因此,按照推荐方式配置的全局 EC 部署具有极高的弹性。但是,在极端故障条件下,读取处理效率可能会降低,因为其他区域的节点保证具有一些与代理服务器已经收到的片段重复的片段。目前正在努力改进代理服务器节点选择策略,以便在需要从其他区域读取时,优先选择可能具有有用片段的节点,而不是可能返回重复片段的节点。
已知问题¶
高效的跨区域重建¶
目前也在努力改进 Global EC 策略的对象重建器效率。与代理服务器不同,重建器在收集片段时不会应用任何读取亲和性设置。因此,它很可能会收到重复的片段(即,发出浪费的后端 GET 请求),同时执行每个片段重建。
此外,正在调查 Global EC 的其他重建器优化
由于片段在区域之间重复,在某些情况下,从另一个区域的副本恢复失败的片段可能比从本地区域的其他片段重建它们更具吸引力。
相反,为了避免 WAN 传输,从本地奇偶校验重建片段可能更具吸引力。
在重新平衡期间,始终从旧主节点恢复到新主节点比从远程区域重建或传输副本更具吸引力。
底层¶
现在我们已经解释了 Swift 中的 EC 支持以及如何配置和使用它,让我们来探讨一下 EC 在螺丝钉级别是如何工作的。
术语¶
术语“片段”已经用于描述 EC 过程的输出(一系列片段),但是我们需要在此定义其他一些关键术语,然后再深入研究。如果不特别注意一致使用正确的术语,很容易很快感到困惑!
chunk:通过线路接收的 HTTP chunk(不用于描述任何 EC 特定操作)。
segment:不要与 SLO/DLO 对该词的使用混淆,在 EC 中,我们称一系列连续的 HTTP chunk 在执行 EC 操作之前缓冲起来为 segment。
fragment:当将擦除编码转换应用于 segment 时,会生成数据和奇偶校验“片段”。
EC archive:EC 片段的串联;对于存储节点,这看起来像一个对象。
ec_ndata:EC 数据片段的数量。
ec_nparity:EC 奇偶校验片段的数量。
中间件¶
中间件保持不变。对于大多数中间件(例如,SLO/DLO),代理服务器对传入对象进行分片这一事实是透明的。但是,对于列表端点,情况就有点不同了。列表端点的调用者将获得所有片段的位置。调用者无法使用此信息重新组装原始对象,但是节点位置对于某些应用程序仍然可能是很有用的信息。
磁盘上的存储¶
EC 档案存储在各自的 objects-N 目录中的磁盘上,基于其策略索引。有关每个策略目录的信息,请参阅 存储策略。
除了对象时间戳之外,EC 档案的文件名还编码了与档案相关的其他信息
档案片段索引。这有几个原因。首先,它允许我们将不同的片段档案存储在同一个存储节点上,这并不常见,但许多情况下是可能的。如果没有针对一组中不同 EC 档案文件的唯一文件名,我们可能会在某些情况下用索引 n 的档案覆盖索引 m 的档案。
索引附加到文件名,紧接在
.data扩展名之前。例如,存储第 5 个片段的档案的文件名将是1418673556.92690#5.data
档案的持久状态。稍后将详细说明其含义,但被认为持久的档案在其文件名中包含一个附加的
#d字符串,紧接在.data扩展名之前。例如1418673556.92690#5#d.data
因此,使用特定于策略的转换函数来构建档案文件名。这些函数在 diskfile 模块中实现为 BaseDiskFileManager 特定子类的该方法。
复制策略的转换函数只是一个 NOP。
注意
在早期版本中,档案的持久状态由一个额外的名为 .durable 的文件表示,而不是 .data 文件名中的 #d 子字符串。上面的示例的 .durable 将是
1418673556.92690.durable
代理服务器¶
高级¶
代理服务器处理纠删编码的方式与复制不同,因此存在一些仅针对 EC 策略的代码路径,无论是通过子类化还是简单的条件语句。更详细地查看 PUT 和 GET 路径将有助于更清楚地理解这一点。但首先,我们来概述一下对象如何流经系统
请注意
传入的对象在代理处被缓冲为片段。
片段在代理处被纠删编码为碎片。
代理将碎片条带化到参与节点,以便将存储在磁盘上的文件(我们称之为碎片存档)与每个新碎片一起追加。
这种方案可以最大限度地减少给定我们的分段和碎片化后的磁盘文件数量。
多阶段会话¶
多部分 MIME 文档支持用于允许代理与存储节点进行握手会话,以处理 PUT 请求。这有几个不同的原因。
从存储节点的角度来看,碎片存档只是另一个对象,我们需要一种机制来发送原始对象的 etag,在所有碎片存档着陆之后。
在不引入强一致性语义的情况下,代理需要一种机制来知道有多少碎片存档实际上已经写入磁盘,然后才能告知客户端 PUT 成功。
MIME 支持代理和存储节点之间针对每个 PUT 的会话。这使我们能够在一个连接中处理 PUT,并确保我们拥有两阶段提交的本质,基本上让代理在确认一组碎片存档已被写入后,与存储节点通信。
在会话的第一阶段,代理需要 ec_ndata + 1 个碎片存档的法定数量成功写入存储节点。这确保了即使其中一个碎片存档不可用,对象仍然可以被重建。如上所述,每个碎片存档文件命名为
<ts>#<frag_index>.data
其中 ts 是时间戳,frag_index 是碎片存档索引。
在会话的第二阶段,代理将确认消息传达给存储节点,确认碎片存档的法定数量已达到。这导致每个存储节点将第一阶段写入的碎片存档重命名为包含子字符串 #d。
<ts>#<frag_index>#d.data
这指示对象服务器该碎片存档是 持久的,并且在时间戳 ts 处有一组持久的数据文件。
在会话的第二阶段,代理需要 ec_ndata + 1 个存储节点上成功的提交的法定数量。这确保了即使其中一个不可用,也有足够的已提交碎片存档来重建对象。重建器确保持久状态在存储节点上得到复制,即使它可能丢失。
请注意,会话的提交阶段的完成也是对象服务器立即删除该对象较早时间戳文件的信号。这一点至关重要,因为我们不希望在存储节点从代理处通过多阶段会话确认其他节点已着陆足够的碎片以达到法定数量之前删除旧对象。
基本流程如下
代理服务器对对象进行纠删编码并将对象碎片(ec_ndata + ec_nparity)流式传输到存储节点。
存储节点将对象存储为 EC 存档,并在完成对象数据/元数据写入后,向代理发送第一阶段响应。
在收到存储节点响应的法定数量后,代理通过向对象服务器发送提交确认来启动第二阶段。
收到提交消息后,对象服务器将
.data文件重命名为包含#d子字符串,表示 PUT 成功,并向代理服务器发送最终响应。代理等待 ec_ndata + 1 个对象服务器以成功(2xx)状态响应,然后才向客户端响应成功状态。
以下是会话的大致示例
proxy: PUT /p/a/c/o
Transfer-Encoding': 'chunked'
Expect': '100-continue'
X-Backend-Obj-Multiphase-Commit: yes
obj: 100 Continue
X-Obj-Multiphase-Commit: yes
proxy: --MIMEboundary
X-Document: object body
<obj_data>
--MIMEboundary
X-Document: object metadata
Content-MD5: <footer_meta_cksum>
<footer_meta>
--MIMEboundary
<object server writes data, metadata to <ts>#<frag_index>.data file>
obj: 100 Continue
<quorum>
proxy: X-Document: put commit
commit_confirmation
--MIMEboundary--
<object server renames <ts>#<frag_index>.data to <ts>#<frag_index>#d.data>
obj: 20x
<proxy waits to receive >=2 2xx responses>
proxy: 2xx -> client
关于碎片存档的持久状态的一些关键点
持久的碎片存档意味着集群的其他地方(持久的和/或非持久的)存在足够的其他碎片存档来重建对象。
当代理执行 GET 时,它将要求至少一个对象服务器响应碎片存档是持久的,然后再重建并将对象返回给客户端。
部分 PUT 失败¶
部分 PUT 失败有几种不同的模式。在一种情况下,代理服务器在整个 PUT 会话期间都处于活动状态。这是一个非常直接的情况。如果碎片存档的法定数量已成功写入其存储节点,则客户端将收到良好的响应。在这种情况下,重建器将发现丢失的碎片存档,执行重建并将这些碎片存档传递到其节点。
更有趣的情况是如果代理在会话中间发生故障会发生什么。如果碰巧已达到法定数量并且完成了提交阶段,那么就像前面的情况一样,重建器将修复问题。但是,如果提交没有机会发生,那么一些存储节点上将有一些 .data 文件(碎片存档),但它们中的任何一个都不知道其他地方是否有足够的碎片来重建整个对象。在这种情况下,客户端不会收到 2xx 响应,因此那里没有问题,但是,让存储节点清理过时的碎片存档。目前正在进行这项工作,以使代理能够在复活这些碎片存档方面发挥作用,但是对于当前版本,在会话开始后但在提交消息之前发生代理故障将简单地导致 PUT 失败。
GET¶
EC 的 GET 与复制不同,因此将 BaseObjectController 子类化为 ECObjectController 能够有效地实现前面描述的高层步骤
代理服务器同时向 ec_ndata 个主对象服务器节点发出请求,目标是在同一时间戳找到一组 ec_ndata 个不同的 EC 存档,以及至少一个对象服务器指示存在持久的碎片存档。如果第一次 ec_ndata 个请求没有实现此目标,则代理服务器将继续向剩余的主节点和转交节点发出请求。
一旦代理服务器找到一组可用的 ec_ndata 个 EC 存档,它将开始调用 PyECLib 以解码对象服务器节点返回的碎片。
代理服务器为客户端响应创建 Etag 和内容长度标头,因为每个 EC 存档的元数据仅对该存档有效。
代理服务器将解码后的数据流式传输回客户端。
请注意,代理不需要所有对象服务器都具有持久的碎片存档才能响应 GET。如果只有一个对象服务器具有与来自其他对象服务器返回的 EC 存档相同时间戳的持久碎片存档,代理将满意。
请注意,对象服务器可能会告知代理服务器它具有不同时间戳和/或碎片索引的多个 EC 存档,这可能会导致代理服务器向该对象服务器发出多个请求以获取不同的 EC 存档。(这种情况在环重新平衡后可能会暂时发生,当存储存档的转交节点已成为主节点并收到其主存档但尚未将转交存档移动到其主节点时。)
代理可能会收到具有不同时间戳的 EC 存档,并且可能会收到多个具有相同索引的 EC 存档。因此,代理在认为 GET 成功之前,会确保它具有相同时间戳和不同碎片索引的足够数量的 EC 存档。
对象服务器¶
对象服务器与代理部分中描述的类似,也支持 MIME 会话。这包括处理提交消息以及解码 MIME 文档的各个部分以提取页脚,其中包含诸如整个对象 etag 之类的内容。
DiskFile¶
纠删编码策略使用子类化的 ECDiskFile、ECDiskFileWriter、ECDiskFileReader 和 ECDiskFileManager 来实现 EC 特定的磁盘文件处理。这包括诸如在文件名中包含碎片索引和持久状态之类的文件名操作,构建 EC 特定的 hashes.pkl 文件以包含碎片索引信息等。
元数据¶
与 EC 关联的元数据有几个不同的类别
系统元数据:EC 具有一组对象级别的系统元数据,它将其附加到每个 EC 存档。元数据仅供内部使用
X-Object-Sysmeta-EC-Etag:原始对象的 Etag。X-Object-Sysmeta-EC-Content-Length:原始对象的内容长度。X-Object-Sysmeta-EC-Frag-Index:对象的碎片索引。X-Object-Sysmeta-EC-Scheme:用于编码对象的 EC 策略的描述。X-Object-Sysmeta-EC-Segment-Size:对象使用的分段大小。
用户元数据:用户元数据不受 EC 影响,但是,EC 存档中存储了完整的用户元数据。这是必需的,因为重建器需要此信息,并且每个重建器仅与其环上的最近邻居通信。
PyECLib 元数据:PyECLib 在每个碎片的基础上存储少量元数据。此元数据未在此处记录,因为它对 Swift 不透明。
数据库更新¶
由于帐户和容器环不与存储策略关联,因此在使用 EC 策略时,这些数据库更新的方式不会发生变化。
重建器¶
重建器执行与复制器类似的功能
从磁盘驱动器故障中恢复。
由于重新平衡而移动数据。
将数据从转交恢复到主节点。
从审计员发现的位腐败中恢复碎片存档。
但是,在底层它以完全不同的方式运行。以下是理解重建器如何运作的一些关键要素。
与复制器不同,重建器所做的工作并不总是像同步或恢复(将数据从转交移回主节点)这两个基本任务那样容易分解,因为一个存储节点可以容纳各种索引的碎片存档,并且每个索引实际上“属于”不同的节点。因此,当复制器从转交恢复数据时,它只需要将数据发送到一个节点,而重建器可以有多个节点。
作业构建和处理¶
由于它需要执行的工作的性质,如上所述,重建器为单个作业处理器构建作业。作业本身包含处理器执行作业所需的所有信息,该作业可能是同步或数据恢复。可能同时执行这两种操作的作业混合在同一后缀目录上。
作业是按分区和碎片索引构建的。也就是说,每个分区中的每个碎片索引都有一个作业。像这样“提前”进行此构建有助于最大限度地减少节点收集 hashes.pkl 信息的交互。
在为分区构建一组作业后,这些作业将被发送到线程进行执行。单个作业处理器然后执行必要的操作,与 ssync 紧密合作以执行其指令。对于数据恢复,实际对象本身是通过 ssync 模块清理的,并且一旦分区中的作业集完成,重建器将尝试删除相关的目录结构。
作业构建必须考虑各种场景,包括
所有碎片索引与本地节点索引匹配的目录分区。这是所有内容都位于正确位置并且我们只需要比较哈希并同步(如果需要)的情况。在这里,我们只需与我们的合作伙伴同步。
至少有一个本地碎片索引以及其他碎片索引的目录分区。在这里,我们需要与碎片索引与 local_id 匹配的合作伙伴同步,所有其他碎片都与它们的主节点同步,然后删除。
没有本地碎片索引但只有一个或多个其他碎片索引的目录分区。在这里,我们只需与我们拥有的碎片索引的主节点同步,然后删除所有本地存档。这是基本的转交恢复情况。
注意
“主节点”是指碎片存档文件名中编码的碎片索引与主分区列表中节点索引匹配的节点。
节点通信¶
复制器与拥有其对象副本的所有节点通信,通常只有其他 2 个节点。对于 EC,让每个重建器节点与所有节点通信会产生大量的开销,因为通常会有更多的节点参与 EC 方案。因此,重建器被构建为仅与其环上的相邻节点通信。这些节点通常被称为合作伙伴。
重建¶
可以认为重建类似于复制,但中间多了一步。重建器被硬编码为使用 ssync 来确定另一方缺少什么以及需要什么。但是,在对象通过网络发送之前,需要从剩余的碎片中重建它,因为本地碎片只是不同的碎片索引,而不是另一方请求的碎片索引。
因此,ssync 中存在针对基于 EC 的策略的钩子。一个案例是基本的重建,从高级别来看,它看起来像这样
确定需要联系哪些节点来收集其他 EC 存档以执行重建。
更新新构建的碎片归档的 etag 和碎片索引元数据元素。
建立与目标节点的连接,并为 ssync 提供一个 DiskFileLike 类,从中可以流式传输数据。
此类中的读取器从节点收集碎片,并使用 PyECLib 重建每个片段,然后再将数据返回给 ssync。本质上,这意味着数据在执行重建的节点上按片段缓冲在内存中,并且每个片段都是动态重建并传递到 ssync_sender,后者将通过 send_put() 方法将它们发送出去。然后,发送者负责在发送对象时删除它们,以防数据回滚。
审计器¶
由于审计器已经基于每个存储策略运行,因此与 EC 没有特定的审计器更改。每个 EC 归档看起来像一个常规对象,并且从审计器的角度被视为一个常规对象。因此,如果审计器在 EC 归档中发现数据损坏,它只需将其隔离,然后重建器将像复制策略的复制器一样处理剩下的事情。