对象业务的追加写接口

类似文件的追加写操作,在对象的末尾增加新的数据内容。

本文有如下假定:

  • 对象存储服务基于文件语义实现。
  • 使用PUT方式上传的对象,内部使用一个文件和对应的元数据来承载。
  • 使用多段方式上传的对象,内部使用多个段文件、元数据来承载,其中每个段文件可管理独有的元数据。

下面讨论追加写操作时的方案和注意事项。

接口定义

业界主流对象存储服务比如AWS S3并未定义追加写操作,而国内的各家公有云对象存储服务基于对象语义和文件语义的理解,提供了对象的追加写操作。

国内的公有云对象存储服务,提供的追加写操作,相关文档的链接(排名不分先后),如下:

阿里云OSSAppendObject为例,接口定义如下:

shell 复制代码
POST /ObjectName?append&position=Position HTTP/1.1
Content-Length:ContentLength
Content-Type: ContentType
Host: BucketName.oss.aliyuncs.com
Date: GMT Date
Authorization: SignatureValue

本接口的关键参数,如下:

  • 对象名,指定执行追加写操作的对象名。
  • 操作名,参数名为append,不需要指定参数值。
  • 追加写的位置,参数名为position,参数值为追加写的开始位置。
  • 数据的长度,使用HTTP头部Content-Length承载。

另外允许更新对象的元数据,对象的元数据的说明见文档

实现思路

考虑到对象上传特点的特点,可以划分为PUT方式上传的对象,和使用多段方式上传的对象,因此在实现追加写时,需要考虑这两种对象和上传方式的差异,借鉴实现思路。

对于PUT方式上传的对象,实现追加写时,可以考虑如下方案:

  • 参照文件的追加写,即把对象当成文件,将追加写操作的数据追加到对象的数据的尾部。
  • 参照多段对象,将追加写的数据当成一个新的段,原有的对象当成多段方式上传的第一个段。

对于多段方式上传的对象,实现追加写时,可以考虑如下方案:

  • 参照文件的追加写,即把对象的最后一个段当成文件,将追加写操作的数据追加到最后一个段的数据的尾部。
  • 参照多段对象,将追加写的数据当成一个新的段,追加到对象的段的尾部。

下面从对象服务特性的角度,分别评估上述方案。

ETag

参考AWS S3数据一致性ETag基于对象的数据,使用MD5算法计算得到。

客户应用在请求的头部增加Content-MD5,指定本次上传数据的MD5值,或者对象数据整体的MD5值。

本次上传数据的MD5

客户应用在请求的头部增加Content-MD5,指定本次上传数据的MD5值。

对象存储服务收到数据后,计算数据的MD5值,并和Content-MD5值对比:

  • 假如二者一致,则判定数据一致。
  • 假如二者不一致,则判定数据不一致。

对象存储服务处理追加写操作成功后,在返回的消息中使用ETag字段,返回服务端依据接收到的数据计算得到的MD5值,方便对象存储的客户应用执行客户端的校验。

本方案中,由于只需要计算追加的数据的MD5值,因此客户应用并不需要获得对象的完整数据。相应的,对象存储的服务端同样只需要计算追加的数据的MD5值,不需要读取对象的完整数据,操作简单。

对象数据整体的MD5

客户应用在请求的头部增加Content-MD5,指定对象数据整体的MD5值。

对象存储服务收到数据后,需要读取对象的数据,结合本次上传的数据,一并计算,得到MD5值。然后和Content-MD5值对比:

  • 假如二者一致,则判定数据一致。
  • 假如二者不一致,则判定数据不一致。

对象存储服务处理追加写操作成功后,在返回的消息中使用ETag字段,返回服务端的对象的MD5值,方便对象存储的客户应用执行客户端的校验。

本方案中,由于需要计算完整对象的MD5值,因此客户应用在执行追加写操作前,本地需要获得对象的全部数据,结合本次追加写操作的数据,一起计算MD5值。相应的,对象存储的服务端需要执行类似的操作,操作流程相对复杂,加大了服务端的负担。

多版本

按照AWS S3多版本中的说明,多版本特性的开关作用在桶级,包含如下状态:

Buckets can be in one of three states:

  • Unversioned (the default)
  • Versioning-enabled
  • Versioning-suspended

当未开启多版本特性,即Unversioned,对象执行追加写操作时,通过直接修改对象的数据来实现。

当开启多版本特性,即Versioning-enabled,有如下选择:

  • 当未指定版本号时,有如下选择:
    • 当前版本为delete-marker,返回失败。
    • 修改对象的当前版本。
    • 新增对象,数据包括原对象的数据和本次追加写的数据。
  • 当指定版本号时,有如下选择:
    • 指定版本是delete-marker,返回失败。
    • 指定版本不是delete-marker,修改对象的指定版本。
    • 返回失败,不允许修改历史版本。

当暂停多版本特性,即Versioning-suspended,有如下选择:

  • 当未指定版本号时,有如下选择:
    • 当前版本为delete-marker,返回失败。
    • 修改对象的当前版本。
  • 当指定版本号时,有如下选择:
    • 指定版本是delete-marker,返回失败。
    • 指定版本不是delete-marker,修改对象的指定版本。
    • 返回失败,不允许修改历史版本。

不过依据前述接口的定义,没有定义versionId,推断不支持修改历史版本,因此可以不考虑这么复杂的特性。

分级

参考AWS S3 归档AWS S3 分级中的说明,处于归档状态的对象,需要先取回才能访问。

显而易见,此处为了维护对象语义,照顾对象存储服务的实现,当对象处于归档状态时,不允许通过调用追加写接口来追加数据。

WORM

参考AWS S3 Object Lock中的说明,开启WORM后:

  • 在保护期内的对象,不允许修改,不允许删除。
  • 在保护期外的对象,不允许修改,允许删除。

因此从维护对象语义的角度讲,在保护期内的对象、保护期外的对象,均不允许通过调用追加写接口来修改对象。

生命周期

参考AWS S3 Lifecycle,追加写操作过程中,被操作的对象可能符合生命周期规则,从而被恰好正在运行的后台任务删除掉。

此时有如下选择:

  • 生命周期的后台任务具备更高的优先级,提前中断追加写操作,正常删除掉对象,对象存储服务对客户应用返回追加写操作失败。
  • 生命周期的后台任务优先级相对较低,跳过当前对象,待下次运行时再决策是否删除。

数据加密

依据SSE-C的说明,客户应用在执行PUT/GET/Head/Copy操作时,均需要提供加密数据的密钥。

即在发起请求时,提供如下头部:

  • x-amz-copy-source​-server-side​-encryption​-customer-algorithm
  • x-amz-copy-source​-server-side​-encryption​-customer-key
  • x-amz-copy-source-​server-side​-encryption​-customer-key-MD5

另外说明对象的元数据和数据均被加密。

事件通知

依据AWS S3 事件通知中的说明,对象存储服务可以提供事件通知,目前支持的事件类型见文档,显然不包括追加写操作,可以扩展事件名,比如:

  • s3:ObjectAppended:Put,使用PUT方式追加。
  • s3:ObjectAppended:Post,使用POST方式追加。

依据AWS S3 消息格式,为了适配追加写操作,可以考虑在object中增加扩展字段,用于说明追加写操作,比如:

  • sizeAppended,本次追加写操作的数据量,单位为bytes
  • eTagAppended,本次追加写操作的数据的MD5值。

并发一致性

依据AWS S3 data consistency model的说明,对象存储服务提供read-after-write的模型。

当多客户端对相同对象并发的发起追加写操作时,实现方案较复杂,需要平衡语义、功能、性能等方面的诉求,设计实现方案。

方案的差异

PUT方式上传对象的文件追加写方案

参照文件的追加写,即把对象当成文件,将追加写操作的数据追加到对象的数据的尾部。

对象规格

使用PUT方式上传的对象,默认小于5G,因此实现时有两种选择,一是维持约束,将5G作为对象大小的上限,二是打破5G的约束,但保持最大规格的约束。

加密存储

可行的实现方案,如下:

  • 方案一

    1. 校验加密算法,保证加密方式一致。
    2. 解密对象已有的数据。
    3. 和追加到末尾的数据。
    4. 完整执行加密操作。
    5. 更新元数据。
    6. 保存元数据和数据。
  • 方案二

    1. 校验加密算法,保证加密方式一致。
    2. 解密元数据。
    3. 加密本次追加到末尾的数据。
    4. 更新元数据。
    5. 保存元数据和数据。

方案二中,在对象的元数据中需要记录每次追加数据的明文、密文的长度,以及加密算法需要保留的信息。

PUT方式上传对象的多段方案

参照多段对象,将追加写的数据当成一个新的段,原有的对象当成多段方式上传的第一个段。

对象规格

维持多段对象的限制,在接口中增加必要的校验,即

  • 单个段的上限为5GiB。
  • 段的数量维持在10000个。

按照上述限制,对象的大小,至多为约48.8TiB。

加密存储

可行的实现方案,如下:

  1. 校验加密算法,保证加密方式一致。
  2. 解密元数据。
  3. 本次追加写的数据,作为单独一个段,执行加密。
  4. 更新元数据。
  5. 保存元数据和数据。

多段方式上传的对象的文件追加写方案

参照文件的追加写,即把对象的最后一个段当成文件,将追加写操作的数据追加到最后一个段的数据的尾部。

对象规格

依据多段对象的限制,如下:

  • 单个段的上限为5GiB。
  • 段的数量维持在10000个。

假如直接在多段对象的最后一个段执行追加写操作,如维持上述约束,则可追加写的数据的总和不超过5GiB,应用场景受限。

假如允许多次追加操作后的数据量超出5GiB,则可能破坏对象语义。

加密存储

可行的实现方案,如下:

  • 方案一

    1. 校验加密算法,保证加密方式一致。
    2. 解密对象的最后一个段的已有的数据。
    3. 和追加到末尾的数据。
    4. 完整执行加密操作。
    5. 更新元数据。
    6. 保存元数据和对象的最后一个段。
  • 方案二

    1. 校验加密算法,保证加密方式一致。
    2. 解密对象的最后一个段的元数据。
    3. 加密本次追加到末尾的数据。
    4. 更新对象的最后一个段的元数据。
    5. 保存对象的最后一个段的元数据和数据。

方案二中,在对象的最后一个段的元数据中需要记录每次追加数据的明文、密文的长度,以及加密算法需要保留的信息。

多段方式上传的对象的多段方案

参照多段对象,将追加写的数据当成一个新的段,追加到对象的段的尾部。

对象规格

维持多段对象的限制,在接口中增加必要的校验,即

  • 单个段的上限为5GiB。
  • 段的数量维持在10000个。

按照上述限制,对象的大小,至多为约48.8TiB。

加密存储

可行的实现方案,如下:

  1. 校验加密算法,保证加密方式一致。
  2. 解密元数据。
  3. 本次追加写的数据,作为单独一个段,执行加密。
  4. 更新元数据。
  5. 保存元数据和数据。

参考资料

对象存储