大型对象支持¶
概述¶
Swift 对单个上传对象的尺寸有限制;默认情况下为 5GB。但是,通过分段的概念,单个对象的下载尺寸几乎是无限的。较大的对象的片段会被上传,并创建一个特殊的清单文件,当下载时,会将所有片段连接成一个对象。这还提供了更快的上传速度,以及并行上传片段的可能性。
动态大型对象¶
提供动态大型对象 (DLO) 支持的中间件。
使用 swift¶
尝试此功能的 quickest 方法是使用包含在 python-swiftclient 库中的 swift Swift 工具。您可以使用 -S 选项来指定分割大文件时使用的分段大小。例如
swift upload test_container -S 1073741824 large_file
这将把 large_file 分割成 1G 的片段,并开始并行上传这些片段。一旦所有片段都上传完毕,swift 将创建清单文件,以便可以将片段下载为一个整体。
因此,以下 swift 命令将下载整个大型对象
swift download test_container large_file
swift 命令对其分段对象支持使用严格的约定。在上面的示例中,它会将所有片段上传到名为 test_container_segments 的第二个容器中。这些片段的名称将类似于 large_file/1290206778.25/21474836480/00000000、large_file/1290206778.25/21474836480/00000001 等。
使用单独容器的主要好处是主容器列表不会被所有片段名称弄乱。使用 <name>/<timestamp>/<size>/<segment> 格式的片段名称的原因是,上传同名的新文件不会覆盖第一个文件的内容,直到更新清单文件的最后一刻。
swift 将为您管理这些片段文件,在删除和覆盖等操作时删除旧片段。如果您希望保留相同大型对象的多个版本,可以使用 --leave-segments 选项覆盖此行为;这很有用。
直接 API¶
您还可以使用 HTTP 请求直接使用片段和清单,而无需让 swift 为您执行此操作。您可以像上传任何其他对象一样上传片段,清单只是一个零字节(未强制执行)的文件,其中包含额外的 X-Object-Manifest 头部。
所有对象片段都需要位于同一个容器中,具有共同的对象名称前缀,并按应连接的顺序排序。对象名称按 UTF-8 字节字符串进行词法排序。它们不必位于与清单文件相同的容器中,这对于保持容器列表的整洁(如上文与 swift 解释的那样)非常有用。
清单文件只是一个零字节(未强制执行)的文件,其中包含额外的 X-Object-Manifest: <container>/<prefix> 头部,其中 <container> 是对象片段所在的容器,<prefix> 是所有片段的公共前缀。
最好先上传所有片段,然后创建或更新清单。这样,在上传完成之前,完整的对象将不可下载。此外,您可以将一组新的片段上传到第二个位置,然后更新清单以指向此新位置。在上传新的片段期间,原始清单仍然可用以下载第一组片段。
注意
使用 POST 请求更新清单对象时,必须包含 X-Object-Manifest 头部,才能使对象继续表现为清单对象。
清单文件不应包含任何内容。但是,这未强制执行。如果清单路径本身符合 X-Object-Manifest 中指定的 container/prefix,并且清单包含一些内容/数据,它也将被视为片段,并且清单的内容将是连接的 GET 响应的一部分。连接的顺序遵循通常的 DLO 逻辑 - 连接的顺序遵循在对片段名称排序后返回的顺序。
这是一个使用 curl 和微小的 1 字节片段的示例
# First, upload the segments
curl -X PUT -H 'X-Auth-Token: <token>' http://<storage_url>/container/myobject/00000001 --data-binary '1'
curl -X PUT -H 'X-Auth-Token: <token>' http://<storage_url>/container/myobject/00000002 --data-binary '2'
curl -X PUT -H 'X-Auth-Token: <token>' http://<storage_url>/container/myobject/00000003 --data-binary '3'
# Next, create the manifest file
curl -X PUT -H 'X-Auth-Token: <token>' -H 'X-Object-Manifest: container/myobject/' http://<storage_url>/container/myobject --data-binary ''
# And now we can download the segments as a single object
curl -H 'X-Auth-Token: <token>' http://<storage_url>/container/myobject
- class swift.common.middleware.dlo.GetContext(dlo, logger)¶
基类:
WSGIContext- get_or_head_response(req, x_object_manifest)¶
- 参数:
req – 用户的请求
x_object_manifest – 作为未引用、原生字符串
- handle_request(req, start_response)¶
接收 GET 或 HEAD 请求,如果它是动态大型对象清单,则返回适当的响应。
否则,直接传递。
静态大型对象¶
提供静态大型对象 (SLO) 支持的中间件。
此功能与动态大型对象 (DLO) 支持非常相似,因为它允许用户并发上传许多对象,然后将它们下载为单个对象。它不同之处在于,它不依赖于最终一致的容器列表来实现这一点。相反,使用用户定义的对象片段清单。
上传清单¶
在用户上传要连接的对象后,会上传清单。请求必须是带有查询参数的 PUT
?multipart-manifest=put
此请求的主体是 JSON 格式的有序片段描述列表。为每个片段提供的数据是
键 |
描述 |
|---|---|
路径 |
指向片段对象的路径(不包括帐户)/container/object_name |
etag |
(可选)在 PUT 对象片段时返回的 ETag |
size_bytes |
(可选)完整片段对象的大小(以字节为单位) |
range |
(可选)要在对象中使用的(包括)范围。如果省略,则使用整个对象 |
或者
键 |
描述 |
|---|---|
data |
base64 编码的数据以供返回 |
注意
必须包含至少一个基于对象的片段。如果您想创建一个完全由数据片段组成的清单,请考虑上传一个普通对象。
列表的格式将是
[{"path": "/cont/object",
"etag": "etagoftheobjectsegment",
"size_bytes": 10485760,
"range": "1048576-2097151"},
{"data": base64.b64encode("interstitial data")},
{"path": "/cont/another-object", ...},
...]
基于对象的片段的数量限制为 max_manifest_segments(可在 proxy-server.conf 中配置,默认值为 1000)。每个片段至少必须为 1 字节。上传时,中间件将对传入的每个基于对象的片段执行 HEAD 操作,以验证
片段存在(即 HEAD 操作成功);
片段满足最小尺寸要求;
如果用户提供非空
etag,则 etag 匹配;如果用户提供非空
size_bytes,则 size_bytes 匹配;并且如果用户提供
range,则它是一个语法正确的单一范围,并且可以根据对象的大小得到满足。
对于内联数据片段,中间件会验证每个片段是否是有效的、非空的 base64 编码的二进制数据。请注意,数据片段不计入 max_manifest_segments。
请注意,etag 和 size_bytes 键是可选的;如果省略,则不执行验证。如果任何对象验证失败(未找到、大小/etag 不匹配、低于最小大小、无效范围),则用户将收到 4xx 错误响应。如果一切匹配,用户将收到 2xx 响应,并且 SLO 对象已准备好进行下载。
请注意,大型清单可能需要很长时间才能验证;从历史上看,客户端需要使用较长的读取超时才能使 Swift 有足够的时间发送最终的 201 Created 或 400 Bad Request 响应。现在,客户端应使用查询参数
?multipart-manifest=put&heartbeat=on
请求 Swift 发送立即的 202 Accepted 响应和定期空白以保持连接活动状态。最终响应代码将出现在主体中。响应主体的格式默认为 text/plain,但根据 Accept 头部可以是 json 或 xml。一个示例主体如下
Response Status: 201 Created
Response Body:
Etag: "8f481cede6d2ddc07cb36aa084d9a64d"
Last Modified: Wed, 25 Oct 2017 17:08:55 GMT
Errors:
或者,作为 json 响应
{"Response Status": "201 Created",
"Response Body": "",
"Etag": "\"8f481cede6d2ddc07cb36aa084d9a64d\"",
"Last Modified": "Wed, 25 Oct 2017 17:08:55 GMT",
"Errors": []}
在后台,成功后,从用户输入生成的 JSON 清单会通过额外的 X-Static-Large-Object: True 头部和修改后的 Content-Type 发送到对象服务器。此清单中的项目将包括每个片段的 etag 和 size_bytes,无论客户端是否指定了它们以进行验证。参数 swift_bytes=$total_size 将附加到现有的 Content-Type,其中 $total_size 是所有包含片段的 size_bytes 之和。此额外的参数将对用户隐藏。
清单文件可以引用单独容器中的对象,这将提高并发上传速度。对象可以被多个清单引用。SLO 清单的片段甚至可以是其他 SLO 清单。将它们视为任何其他对象,即使用 PUT 对象片段时给出的 Etag 和 Content-Length 到父 SLO。
在上传清单时,用户可以发送 Etag 进行验证。如果未指定范围,则它应该是片段 etags 的 md5。例如,如果要上传的清单如下所示
[{"path": "/cont/object1",
"etag": "etagoftheobjectsegment1",
"size_bytes": 10485760},
{"path": "/cont/object2",
"etag": "etagoftheobjectsegment2",
"size_bytes": 10485760}]
上述清单的 Etag 将是 etagoftheobjectsegment1 和 etagoftheobjectsegment2 的 md5。可以使用以下方式计算
echo -n 'etagoftheobjectsegment1etagoftheobjectsegment2' | md5sum
如果要上传的清单带有片段范围如下所示
[{"path": "/cont/object1",
"etag": "etagoftheobjectsegmentone",
"size_bytes": 10485760,
"range": "1-2"},
{"path": "/cont/object2",
"etag": "etagoftheobjectsegmenttwo",
"size_bytes": 10485760,
"range": "3-4"}]
在计算上述清单的 Etag 时,内部将以 etagvalue:rangevalue; 的形式获取每个片段的 etag。因此,上述清单的 Etag 将是
echo -n 'etagoftheobjectsegmentone:1-2;etagoftheobjectsegmenttwo:3-4;' \
| md5sum
对于 Etag 计算,内联数据片段被认为具有原始数据的 md5(即不是 base64 编码的)。
范围规范¶
用户现在能够指定 SLO 片段的范围。用户可以在片段描述中包含可选的 range 字段,以指定应从底层对象使用的字节。每个片段只能指定一个范围。
注意
etag 和 size_bytes 字段仍然描述整个底层对象。
如果用户上传此清单
[{"path": "/con/obj_seg_1", "size_bytes": 2097152, "range": "0-1048576"},
{"path": "/con/obj_seg_2", "size_bytes": 2097152,
"range": "512-1550000"},
{"path": "/con/obj_seg_1", "size_bytes": 2097152, "range": "-2048"}]
该片段将由 /con/obj_seg_1 的前 1048576 字节、/con/obj_seg_2 的字节 513 到 1550000(包括)以及 /con/obj_seg_1 的字节 2095104 到 2097152(即最后 2048 字节)组成。
注意
最小范围大小为 1 字节。这与最小片段大小相同。
内联数据规范¶
在上传清单时,用户可以包含应包含在内的对象中的“data”片段。这些片段中的数据必须是 base64 编码的二进制数据,并且将以与将该数据上传并引用为单独对象相同的方式包含在生成的大型对象的 etag 中。
注意
此功能主要旨在减少存储大量小对象的需求,因此提供的任何数据都必须适合最大清单大小(默认值为 8MiB)。可以通过 proxy-server.conf 中的 max_manifest_size 进行配置。
检索大型对象¶
向清单对象发送 GET 请求将返回清单中对象的串联,类似于 DLO。如果清单中的任何片段未找到,或者它们的 Etag / Content-Length 自上传以来已更改,则连接将断开。在这种情况下,将在代理日志中记录 409 Conflict,并且用户将收到不完整的结果。请注意,无论用户是否在上传期间执行了逐片段验证,都将强制执行此操作。
此 GET 或 HEAD 请求的标头将返回附加到清单对象本身的元数据,但有一些例外情况
标头 |
值 |
|---|---|
Content-Length |
SLO 的总大小(清单中片段大小之和) |
X-Static-Large-Object |
字符串“True” |
Etag |
SLO 的 etag(以与 DLO 相同的方式生成) |
带有查询参数的 GET 请求
?multipart-manifest=get
将返回原始清单的转换版本,其中包含额外的字段和不同的键名。例如,上述示例中的第一个清单如下所示
[{"name": "/cont/object",
"hash": "etagoftheobjectsegment",
"bytes": 10485760,
"range": "1048576-2097151"}, ...]
如您所见,与 put 请求相比,某些字段已重命名:path 是 name,etag 是 hash,size_bytes 是 bytes。range 字段保持不变(如果存在)。
带有查询参数的 GET 请求
?multipart-manifest=get&format=raw
将返回客户端发送的原始清单的内容。这两个调用的主要目的是仅用于调试。
带有查询参数的 GET 请求
?part-number=<n>
将返回第 nth 个片段的内容。片段索引从 1 开始,因此 n 必须是 1 到清单中总片段数之间的整数。响应状态将为 206 Partial Content,其标头将包括:一个 X-Parts-Count 标头,等于总片段数;一个 Content-Length 标头,等于指定片段的长度;一个 Content-Range 标头,描述 SLO 中指定部分的字节范围。带有 part-number 参数的 HEAD 请求也将返回状态为 206 Partial Content 且具有相同标头的响应。
注意
当上传清单对象时,几乎可以保证清单中的每个片段都存在并且符合规范。但是,没有任何东西可以阻止用户通过删除/替换清单中引用的片段来破坏 SLO 下载。用户有责任谨慎处理片段。
删除大型对象¶
一个 DELETE 请求将只删除清单对象本身。清单引用的片段数据将保持不变。
带有查询参数的 DELETE
?multipart-manifest=delete
将删除清单中引用的所有片段,然后删除清单本身。失败响应将类似于批量删除中间件。
带有查询参数的 DELETE
?multipart-manifest=delete&async=yes
将异步调度清单中引用的所有片段以供删除,然后删除清单本身。请注意,在对象过期程序清理它们之前,片段将继续出现在列表和配额计数中。只有当所有片段位于同一容器中且其中任何一个都不是嵌套的 SLO 时,此选项才可用。
修改大型对象¶
PUT 和 POST 请求将按预期工作;例如,PUT 将只覆盖清单对象。
容器列表¶
在容器列表中,SLO 清单对象的列出大小将是清单中串联片段的 total_size。容器(以及随后帐户)的整体 X-Container-Bytes-Used 不会反映清单的 total_size,而是存储的 JSON 数据的实际大小。造成这种有些令人困惑的差异的原因是,我们希望容器列表反映下载时清单对象的大小。但是,我们不希望在容器和帐户元数据中两次计算已用字节数(对于清单和它引用的片段),这些元数据可用于统计和计费目的。
- class swift.common.middleware.slo.RespAttrs(is_slo, timestamp, manifest_etag, slo_etag, slo_size)¶
基类:
object封装处理潜在 SLO 响应相关的 GET 或 HEAD 响应的属性。
此类实例通常使用
from_headers方法构造。- 参数:
is_slo – 如果响应似乎是 SLO 清单,则为 True,否则为 False。
timestamp –
Timestamp的实例。manifest_etag – 清单对象的 Etag,如果
is_slo为 False,则为 None。slo_etag – SLO 的 Etag。
slo_size – SLO 的大小。
- classmethod from_headers(response_headers)¶
检查响应标头并提取我们可以找到的任何 resp_attrs。
- 参数:
response_headers – 对象响应中的元组列表
- 返回值:
RespAttrs 的实例,用于表示响应标头
- update_from_segments(segments)¶
始终在 SLO 获取清单响应体时调用,对于旧版清单,我们将计算我们无法从 sys-meta 标头中获取的大小/etag 值。
- class swift.common.middleware.slo.SloGetContext(slo)¶
基类:
WSGIContext- convert_segment_listing(resp_iter)¶
将清单数据转换为与通过 ?multipart-manifest=put 放入的格式匹配。
- 参数:
resp_iter – 响应迭代器
- 引发:
HTTPServerError –
- 返回值:
json 序列化的原始格式(字节)
- class swift.common.middleware.slo.StaticLargeObject(app, conf, max_manifest_segments=1000, max_manifest_size=8388608, yield_frequency=10, allow_async_delete=True)¶
基类:
objectStaticLargeObject 中间件
请参阅上方获取完整说明。
为任何子请求创建的代理日志将具有 swift.source 设置为“SLO”。
- 参数:
app – paste.deploy 链中的下一个 WSGI 过滤器或应用程序。
conf – 中间件的配置字典。
max_manifest_segments – 新创建的静态大型对象中允许的最大片段数。
max_manifest_size – 新创建的静态大型对象清单的最大大小(以字节为单位)。
yield_frequency – 如果客户端在创建新的静态大型对象时在查询参数中包含
heartbeat=on,则在发送空格以保持连接活动之间等待的时间段。
- get_segments_to_delete_iter(req)¶
一个生成器函数,用于删除清单中引用的所有片段和子片段。
- 参数:
req – 带有 SLO 清单路径的
Request- 引发:
HTTPPreconditionFailed – 请求路径中的无效 UTF8
HTTPBadRequest – 缓冲的子片段过多以及 SLO 清单路径无效
- get_slo_segments(obj_name, req)¶
执行
Request并返回 SLO 清单的片段。- 参数:
obj_name – 要删除的对象的名称,格式为
/container/objectreq – 基本
Request
- 引发:
HTTPServerError – 无法加载 obj_name 或无法加载 SLO 清单数据。
HTTPBadRequest – 不是 SLO 清单
HTTPNotFound – 未找到 SLO 清单
- 返回值:
SLO 清单的片段
- handle_multipart_delete(req)¶
将删除 SLO 清单中的所有片段,如果成功,则删除清单文件。
- 参数:
req – 带有路径中 obj 的
Request- 返回值:
swob.Response 其 app_iter 设置为 Bulk.handle_delete_iter
- swift.common.middleware.slo.calculate_byterange_for_part_num(req, segments, part_num)¶
辅助函数,用于计算 part_num 响应的字节范围。
注意:作为计算 part_num 响应所需的字节范围的单个元组的副作用,此函数还会修改请求的 Range 标头,以便 swob 知道返回 206。
- 参数:
req – 请求对象
segments – seg_dicts 列表
part_num – 要返回对象的部件编号
- 返回值:
表示字节范围的元组
- swift.common.middleware.slo.calculate_byteranges(req, segments, resp_attrs, part_num)¶
根据请求、片段和部件号计算字节范围。
注意:作为计算 part_num 响应所需的字节范围的单个元组的副作用,此函数还会修改请求的 Range 标头,以便 swob 知道返回 206。
- 参数:
req – 请求对象
segments – seg_dicts 列表
resp_attrs – slo 响应属性
part_num – 要返回对象的部件编号
- 返回值:
表示字节范围的元组列表
- swift.common.middleware.slo.parse_and_validate_input(req_body, req_path)¶
给定请求体,对其进行解析并返回字典列表。
输出结构与输入结构几乎相同,但并非完全相同。给定有效的对象支持的输入字典
d_in,其对应的输出字典d_out将如下所示d_out[‘etag’] == d_in[‘etag’]
d_out[‘path’] == d_in[‘path’]
d_in[‘size_bytes’] 可以是字符串(“12”)或整数(12),但 d_out[‘size_bytes’] 是一个整数。
(可选) d_in[‘range’] 是形式为“M-N”、“M-”或“-N”的字符串,其中 M 和 N 是非负整数。d_out[‘range’] 是相应的 swob.Range 对象。如果 d_in 没有键 ‘range’,则 d_out 也没有。
内联数据字典将去除任何多余的填充。
- 引发:
解析错误或语义错误时的 HTTPException(例如,错误的 JSON 结构、语法上无效的范围)
- 返回值:
成功时返回字典列表
直接 API¶
SLO 支持围绕用户生成的清单文件展开。在用户将片段上传到其帐户后,需要构建并上传清单文件。所有对象片段的大小都必须至少为 1 字节。有关更多详细信息,请参阅 SLO 文档中的 静态大对象。
附加说明¶
使用
GET或HEAD请求清单文件时,将返回带有串联对象的X-Object-Manifest: <container>/<prefix>标头,以便您可以确定其片段来自哪里。使用 POST 请求更新清单对象时,必须包含
X-Object-Manifest头部,才能使对象继续表现为清单对象。对于
GET或HEAD请求清单文件的响应的Content-Length将是<container>/<prefix>列出中所有片段的总和,动态计算。因此,在创建清单后上传额外的片段将使串联对象变得更大;无需重新创建清单文件。对于
GET或HEAD请求清单文件的响应的Content-Type将与创建清单的PUT请求期间设置的Content-Type相同。您可以轻松地通过重新发出PUT请求来更改Content-Type。对于
GET或HEAD请求清单文件的响应的ETag将是清单中每个片段的 ETag 串联字符串的 MD5 总和(对于 DLO,来自列出<container>/<prefix>)。通常在 Swift 中,ETag 是对象内容的 MD5 总和,并且对于每个片段而言都是如此。但是,对于清单本身生成这样的 ETag 没有意义,因此选择了这种方法至少可以提供更改检测。
注意
如果您正在使用容器同步功能,则需要确保您的清单文件和片段文件都已同步,如果它们恰好位于不同的容器中。
历史¶
动态大对象支持经历了各种迭代,然后才确定这种实现方式。
驱动 Swift 中对象大小限制的主要因素是维护环分区之间的平衡。为了在整个群集中保持磁盘使用量的均匀分布,显而易见的存储模式是简单地将较大的对象拆分为较小的片段,然后在读取时将它们粘合在一起。
在引入大对象支持之前,一些应用程序已经将上传拆分为片段,并在检索各个部分后在客户端重新组装它们。这种设计允许客户端支持大型数据集的备份和存档,但也经常用于提高性能或减少由于网络中断造成的错误。这种方法的的主要缺点是,需要了解原始分区方案才能正确地重新组装对象,这对于某些用例(例如 CDN 起源)是不切实际的。
为了消除任何障碍,让客户端想要存储大于 5GB 的对象,我们最初还原型化了对大对象上传的完全透明支持。完全透明的实现将通过在代理中上传期间自动将对象拆分为片段来支持更大的最大大小,而无需更改客户端 API。所有片段都完全隐藏在客户端 API 之外。
此解决方案为群集引入了许多具有挑战性的故障条件,不会为客户端提供任何并行上传的选项,并且没有恢复功能的基础。透明的实现被认为对于其带来的好处而言过于复杂。
选择当前的“用户清单”设计是为了为客户端提供大型对象的透明下载,并仍然为上传客户端提供一个干净的 API 来支持分段上传。
为了满足尽可能多的用例,Swift 支持两种类型的大对象清单。动态和静态大对象清单都支持相同的想法,即允许用户上传许多片段,稍后将其下载为单个文件。
动态大对象依赖于容器列表来提供清单。这具有允许用户随时添加/删除清单片段的优点。它具有依赖于最终一致的容器列表的缺点。必须更新所有三个容器数据库才能保证完整的列表。此外,所有片段必须位于单个容器中,这可能会限制并发上传速度。
静态大对象依赖于用户提供的清单文件。用户可以将对象上传到多个容器,然后在自行生成的文件中引用这些对象(片段)。未来 GET 到该文件的请求将下载指定片段的串联。这具有在清单成功 PUT 后立即下载完整对象的优点。能够将片段上传到单独的容器还可以提高并发上传速度。它具有清单一旦 PUT 就会完成的缺点。对其进行任何更改意味着必须替换它。
在这两种方法之间,用户在如何选择将大型对象上传和检索到 Swift 的方式上具有很大的灵活性。但是,Swift 不会阻止用户伤害自己。在任何一种情况下,片段都可以随时被用户删除。如果片段被意外删除,动态大对象,不知道它曾经存在,会愉快地忽略已删除的文件,并且用户将获得不完整的文件。静态大对象在无法检索清单中指定的对象时,将中断连接,并且用户将收到部分结果。