类似文件的追加写操作,在对象的末尾增加新的数据内容。
本文有如下假定:
- 对象存储服务基于文件语义实现。
- 使用PUT方式上传的对象,内部使用一个文件和对应的元数据来承载。
- 使用多段方式上传的对象,内部使用多个段文件、元数据来承载,其中每个段文件可管理独有的元数据。
下面讨论追加写操作时的方案和注意事项。
接口定义
业界主流对象存储服务比如AWS S3并未定义追加写操作,而国内的各家公有云对象存储服务基于对象语义和文件语义的理解,提供了对象的追加写操作。
国内的公有云对象存储服务,提供的追加写操作,相关文档的链接(排名不分先后),如下:
以阿里云OSS 的AppendObject为例,接口定义如下:
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的约束,但保持最大规格的约束。
加密存储
可行的实现方案,如下:
-
方案一
- 校验加密算法,保证加密方式一致。
- 解密对象已有的数据。
- 和追加到末尾的数据。
- 完整执行加密操作。
- 更新元数据。
- 保存元数据和数据。
-
方案二
- 校验加密算法,保证加密方式一致。
- 解密元数据。
- 加密本次追加到末尾的数据。
- 更新元数据。
- 保存元数据和数据。
方案二中,在对象的元数据中需要记录每次追加数据的明文、密文的长度,以及加密算法需要保留的信息。
PUT方式上传对象的多段方案
参照多段对象,将追加写的数据当成一个新的段,原有的对象当成多段方式上传的第一个段。
对象规格
维持多段对象的限制,在接口中增加必要的校验,即
- 单个段的上限为5GiB。
- 段的数量维持在10000个。
按照上述限制,对象的大小,至多为约48.8TiB。
加密存储
可行的实现方案,如下:
- 校验加密算法,保证加密方式一致。
- 解密元数据。
- 本次追加写的数据,作为单独一个段,执行加密。
- 更新元数据。
- 保存元数据和数据。
多段方式上传的对象的文件追加写方案
参照文件的追加写,即把对象的最后一个段当成文件,将追加写操作的数据追加到最后一个段的数据的尾部。
对象规格
依据多段对象的限制,如下:
- 单个段的上限为5GiB。
- 段的数量维持在10000个。
假如直接在多段对象的最后一个段执行追加写操作,如维持上述约束,则可追加写的数据的总和不超过5GiB,应用场景受限。
假如允许多次追加操作后的数据量超出5GiB,则可能破坏对象语义。
加密存储
可行的实现方案,如下:
-
方案一
- 校验加密算法,保证加密方式一致。
- 解密对象的最后一个段的已有的数据。
- 和追加到末尾的数据。
- 完整执行加密操作。
- 更新元数据。
- 保存元数据和对象的最后一个段。
-
方案二
- 校验加密算法,保证加密方式一致。
- 解密对象的最后一个段的元数据。
- 加密本次追加到末尾的数据。
- 更新对象的最后一个段的元数据。
- 保存对象的最后一个段的元数据和数据。
方案二中,在对象的最后一个段的元数据中需要记录每次追加数据的明文、密文的长度,以及加密算法需要保留的信息。
多段方式上传的对象的多段方案
参照多段对象,将追加写的数据当成一个新的段,追加到对象的段的尾部。
对象规格
维持多段对象的限制,在接口中增加必要的校验,即
- 单个段的上限为5GiB。
- 段的数量维持在10000个。
按照上述限制,对象的大小,至多为约48.8TiB。
加密存储
可行的实现方案,如下:
- 校验加密算法,保证加密方式一致。
- 解密元数据。
- 本次追加写的数据,作为单独一个段,执行加密。
- 更新元数据。
- 保存元数据和数据。