今天有同学在群里问起 ETag 相关的问题,想起了一些对象存储 ETag 相关的有趣细节。

首先,ETag 实现要求必须由 " 包裹,比如 S3 的 HeadObject 响应:

HTTP/1.1 200 OK
x-amz-id-2: ef8yU9AS1ed4OpIszj7UDNEHGran
x-amz-request-id: 318BC8BC143432E5
x-amz-version-id: 3HL4kqtJlcpXroDTDmjVBH40Nrjfkd
Date: Wed, 28 Oct 2009 22:32:00 GMT
Last-Modified: Sun, 1 Jan 2006 12:00:00 GMT
ETag: "fba9dede5f27731c9771645a39863328"
Content-Length: 434234
Content-Type: text/plain
Connection: close
Server: AmazonS3      

其次,ETag 的语义是用来区分资源的不同版本,所以它的生成方式是由实现自行决定的。可以是内容的 Hash,最后修改时间的 Hash 甚至是自己定义的版本号,而对象存储生成的 ETag 与内容的上传方式有关。

常见对象存储上传文件的方式有以下几种:

  • PostObject & PutObject
  • Append Object (追加写入)
  • Multipart Uploads (上传时指定 part number,最后使用 part number 组合成对象)
  • Block Uploads (上传时返回 block id,最后使用 block ids 组合成对象)
  • Page Uploads (上传时指定 Range)

对于 PostObject & PutObject 来说,一般的对象存储服务都会限制最大 5GB。所以可以在上传的时候就计算出它的 Hash 并写入 Object 的元数据中。对于绝大多数对象存储服务来说,通常使用的算法是 MD5,也就是说与 Content-MD5 Header 的取值相同(语义是不同的)。

对于其他的上传方式来说,ETag 就不一定是 MD5 了。因为 MD5 算法并不是一个滚动 Hash 算法,我们无法在已知 AB MD5 值的情况下来计算出 A+B 的 MD5。考虑到这些上传方式最后组成的文件会达到 TB 量级,从头读到尾来计算 MD5 显然收益不高,所以我们只能使用其他的算法来生成 ETag。以分段上传为例,比较常见的做法是把每一个 Part 的 ETag 组合起来计算它的 MD5 并返回。

所以在开发应用的时候需要考虑到这些,不能简单地把 ETag 视作对象内容的 Content-MD5。这也是为什么 go-storage 会提出 Normalize content hash check 这个草案的原因。


这周在忙活 AOS 参加 开源软件供应链点亮计划 - 暑期2021 的事情,目前组织的审核已经通过,正在准备提交项目。对我们项目感兴趣的在校同学欢迎来 event-ospp-summer-2021 聊聊~


下周再见!