请求前提条件

本页面讨论了请求前提条件;您可以使用这些前提条件,防止资源在处于意外状态时被应用于资源。

简介

在对 Cloud Storage 的请求中使用前提条件时,仅当目标资源满足前提条件中定义的条件时,该请求才会继续。前提条件检查可确保存储桶或对象处于预期状态,以便执行安全的“读取-修改-写入”更新和条件操作。

前提条件通常用于防止更改请求中的竞态条件,例如上传、删除或元数据更新。当同一请求被反复发送,或独立进程每次尝试修改同一资源时,都可能会出现竞态条件。如需了解详情,请参阅竞态条件和数据损坏的示例。在检索连续请求中的对象元数据和数据时,通常也会使用前提条件,以确保对象在两个请求之间的时间保持不变。

前提条件

Cloud Storage 支持在前提条件中使用几种不同的不可变资源属性:

下表列出了 JSON API 和 XML API 支持的前提条件:

JSON API XML API 说明
ifGenerationMatch 查询参数 x-goog-if-generation-match 标头 如果目标资源的 generation 与前提条件中使用的值匹配,则请求继续。如果值不匹配,则请求会失败并显示 412 Precondition Failed 响应。
ifMetagenerationMatch 查询参数 x-goog-if-metageneration-match 标头 如果目标资源的 metageneration 与前提条件中使用的值匹配,则请求继续。如果值不匹配,则请求会失败并显示 412 Precondition Failed 响应。
ifGenerationNotMatch 查询参数 不适用 如果目标资源的 generation 与前提条件中使用的值不匹配,则请求继续。如果值匹配,则请求会失败并显示 304 Not Modified 响应。
ifMetagenerationNotMatch 查询参数 不适用 如果目标资源的 metageneration 与前提条件中使用的值不匹配,则请求继续。如果值匹配,则请求会失败并显示 304 Not Modified 响应。
If-Match 标头 If-Match 标头 适用于检索数据的请求。如果目标资源的 ETag 与前提条件中使用的值匹配,则请求继续。如果值不匹配,则请求会失败并显示 412 Precondition Failed 响应。
If-None-Match 标头 If-None-Match 标头 适用于检索数据的请求。如果目标资源的 ETag 与前提条件中使用的值不匹配,则请求继续。如果值匹配,则请求会失败并显示 304 Not Modified 响应。
不适用 If-Modified-Since 标头 如果目标资源的 Last-Modified 日期在前提条件中使用的值之后,则请求继续。如果目标资源不符合此前提条件,则请求会失败并显示 304 Not Modified 响应。
不适用 If-Unmodified-Since 标头 如果目标资源的 Last-Modified 日期早于或等于前提条件中使用的值,则请求继续。如果目标资源不符合此前提条件,则请求会失败并显示 412 Precondition Failed 响应。

对象组合前提条件

执行对象组合时,JSON APIXML API 均支持以下各项:

  • 目标对象的 generation-match 和 metageneration-match 前提条件。

  • 每个源对象的 generation-match 前提条件。如果独立进程会覆盖组合的某个预期组件,则使用此前提条件可防止使用不正确的组件。如果您使用前提条件并且发生此类覆盖,则 compose 操作将失败并显示 412 Precondition Failed 响应。

对象复制前提条件

在 Cloud Storage 中复制或重写对象时,JSON APIXML API 都支持对目标对象使用标准前提条件。每个 API 都会对源对象提供附加前提条件支持:

  • JSON API 支持源对象的世代和元数据世代前提条件(使用以 ifSource 为前缀的查询参数来指定)。

  • XML API 支持的所有前提条件都可用于源对象。这些前提条件在前缀为 x-goog-copy-source- 的标头中指定。

generation-match 前提条件中的 0

generation-match 先决条件接受值 0 作为特殊情况。 如果请求中包含值为 0 的 generation-match 前提条件,则仅当存储桶中不存在具有指定名称的对象或存储桶中只存在非当前对象版本时,请求才会继续进行。如果存在具有指定名称的现行版本,则请求将失败并显示状态代码 412 Precondition Failed

最佳做法和注意事项

  • 您可以在单个请求中使用多个前提条件。如果未满足任何前提条件,则整个请求将会失败。

  • 存储桶没有世代编号,但存储桶具有元数据世代编号。不应使用在存储桶请求中指定世代编号的前提条件。

  • 如果您在对象请求中使用元数据世代前提条件,则应始终使用世代前提条件。这可防止请求在恰好具有传递前提条件的世代编号的其他对象上完成。

  • 对于同时具有有效和非当前对象版本的存储桶,对象请求不适用于非当前版本,除非世代编号明确包含在请求中。这意味着,对于使用前提条件的常规请求,无论当前版本与前提条件是否匹配,该请求都会失败。

  • 您通常应使用世代前提条件和元数据世代前提条件,而不是使用 ETag 前提条件。世代编号和元数据世代编号相结合,可以跟踪所有对象更新(包括元数据更改),因此能够提供比 ETag 更加强大的保障。此外,世代编号和元数据世代编号在各个 API 之间是一致的,而 ETag 则不是

  • 不能在 XML API 分段上传中使用前提条件。如果您尝试停用该功能,则会导致 400 NotImplemented 错误。

前提条件的费用

许多使用前提条件的架构要求您在主请求之前发出对象元数据请求,以确定当前世代和/或元数据世代编号:

  • 额外的请求意味着您可以通过添加额外的往返,将整体操作延迟时间的网络部分加倍,这是对延迟时间敏感的操作的重要因素。

根据您的应用情况,有一些方法可以减少使用前提条件的影响,例如:

  • 在本地存储对象的世代编号和元数据世代编号,以便您提前知晓要在前提条件中使用的正确编号。
  • 让您的应用知晓哪些对象是新创建的,这样一来,您就可以提前知道应在何时使用 if-generation-match:0 前提条件。

示例:使用前提条件

以下示例在上传对象的请求中使用 generation-match 先决条件。为了让请求继续,存储桶中必须存在具有指定名称的现有对象,并且预先存在的对象世代编号必须与前提条件:

命令行

--if-generation-match 标志与常规命令搭配使用:

gcloud storage cp OBJECT_LOCATION gs://DESTINATION_BUCKET_NAME --if-generation-match=GENERATION

其中:

  • GENERATION 是要替换的对象的预期世代编号。例如 1122334455667788

  • OBJECT_LOCATION 是对象的本地路径。 例如 Desktop/dog.png

  • DESTINATION_BUCKET_NAME 是对象要上传到的存储桶的名称。例如 my-bucket

JSON API

  1. 安装并初始化 gcloud CLI,以便为 Authorization 标头生成访问令牌。

  2. 使用 cURL,通过 POST Object 请求调用 JSON API:

    curl -X POST --data-binary @OBJECT_LOCATION \
      -H "Authorization: Bearer $(gcloud auth print-access-token)" \
      -H "Content-Type: OBJECT_CONTENT_TYPE" \
      "https://storage.googleapis.com/upload/storage/v1/b/BUCKET_NAME/o?uploadType=media&name=OBJECT_NAME"&ifGenerationMatch=GENERATION"

    其中:

    • OBJECT_LOCATION 是对象的本地路径。例如 Desktop/dog.png
    • OBJECT_CONTENT_TYPE 是该对象的内容类型,例如 image/png
    • BUCKET_NAME 是对象要上传到的存储桶的名称。例如 my-bucket
    • OBJECT_NAME 是您要为对象指定的名称。例如 dog.png
    • GENERATION 是要替换的对象的预期世代编号。例如 1122334455667788

XML API

  1. 安装并初始化 gcloud CLI,以便为 Authorization 标头生成访问令牌。

  2. 使用 cURL,通过 PUT Object 请求调用 XML API:

    curl -X PUT --data-binary @OBJECT_LOCATION \
      -H "Authorization: Bearer $(gcloud auth print-access-token)" \
      -H "Content-Type: OBJECT_CONTENT_TYPE" \
      -H "x-goog-if-generation-match: GENERATION" \
      "https://storage.googleapis.com/BUCKET_NAME/OBJECT_NAME"

    其中:

    • OBJECT_LOCATION 是对象的本地路径。例如 Desktop/dog.png
    • OBJECT_CONTENT_TYPE 是该对象的内容类型,例如 image/png
    • GENERATION 是要替换的对象的预期世代编号。例如 1122334455667788
    • BUCKET_NAME 是对象要上传到的存储桶的名称。例如 my-bucket
    • OBJECT_NAME 是您要为对象指定的名称。例如 dog.png

使用前提条件的场景

以下场景探讨了从使用前提条件中受益的竞态条件和缓存示例。

多次重试请求

Cloud Storage 是一个分布式系统。由于请求可能会因网络或服务状况而失败,因此重试失败的推荐方法是使用指数退避算法。但是,由于分布式系统的性质,这些重试操作有时会导致令人惊讶的行为。

请考虑以下情况:您想要删除存储在其中一个存储桶中的对象 file.txt。之后,您希望将具有相同名称的新对象添加到该存储桶中。为了完成此操作,您发出删除请求以删除该对象。但是,网络条件(例如,中间路由器暂时失去连接)会阻止请求到达 Cloud Storage,因此您不会收到响应。

由于没有收到第一个请求的响应,您对该对象发出第二个删除请求。这一次请求成功了,并且您收到了确认删除对象的响应。一分钟后,您上传一个新的 file.txt,并且上传成功。

如果连接中断的路由器随后重新建立连接,并将原本好像丢失的删除请求继续发送到 Cloud Storage,则会出现竞态条件。当请求到达 Cloud Storage 时,请求会成功,因为存在新的 file.txt。Cloud Storage 会发送一则响应,而您不会收到此响应,因为您的客户端已停止侦听响应。 不仅新文件会被删除(与您的意图相反),而且您还不知道发生了第二次删除操作。

下图显示了上述过程:

防止出现竞态条件

为防止出现上述情况,您应首先获取 file.txt 的元数据,以确定它的当前世代。 然后,您可以在 generation-match 前提条件中使用该世代,该前提条件包含在删除请求中。该前提条件可以确保只有具有该特定世代编号的对象才会被删除(无论删除请求何时到达 Cloud Storage,或发送了多少次具有该前提条件的删除请求)。任何意外尝试删除其他世代 file.txt 的操作都将失败并显示响应代码 412 Precondition Failed

由于类似的网络中断可能会导致上传请求(在您的删除请求之后发出)出现竞态条件,因此您可以通过使用上传请求中包含的 generation-match 前提条件中的 0来避免出现许多此类竞态条件。使用此前提条件可以确保重试的上传作业不会意外地将对象写入两次,因为此前提条件规定,仅当对象当前不存在任何世代时,请求才能继续。

借助这些前提条件,您可以在执行删除和上传请求时防止数据意外丢失。下图显示了此过程:

对象元数据关联

对象的数据和元数据是单独的实体,它们共同在 Cloud Storage 中定义该对象。由于它们是单独存在的,因此您在使用对象元数据时可以更改对象数据。

考虑以下情况:

  • 您想要下载某个对象的元数据和数据,该对象必须在两个单独的请求中从 Cloud Storage 检索。您首先请求对象元数据,但在请求对象数据之前,独立进程或用户会替换对象。您对对象数据的请求仍然成功,但现在拥有旧对象的元数据和新对象的数据。

  • 您想要更新对象的元数据,因此需要检索该对象的当前元数据以确定其当前状态。在发送请求以使用所需的修改更新元数据之前,需通过某个独立进程或由某个用户替换该对象。您发出的更改新对象元数据的请求仍然成功,但关联的对象数据与预期不同。

防止出现竞态条件

为防止出现这些情况,您应该在对象元数据的初始请求中使用返回的世代编号,然后在第二个请求的 generation-match 前提条件中使用此值。这样做可确保元数据与数据正确匹配,或者第二个请求失败并显示 412 Precondition Failed 响应代码,以便您为新对象请求正确的元数据。

如果您担心对象元数据可能会在第一个请求和第二个请求之间发生变化,您还可以复制在初始请求中找到的元数据世代编号,并在第二个请求的 metageneration-match 前提条件中使用它。

本地副本时效性

如果您有存储在 Cloud Storage 中的对象的本地副本,则通常希望本地副本与存储在存储桶中的副本保持同步。但是,如果存储在存储桶中的对象没有更改,您不会希望浪费时间和资源再次下载,尤其是对象很大时。

为了防止不必要地下载仍具有时效性的内容,您可以将本地副本的世代编号用作下载请求中包含的 generation-not-match 前提条件中的值:

  • 如果存储桶中的数据仍与您的本地副本匹配,则世代编号匹配,从而导致前提条件失败。因此,整个请求会失败并显示 304 Not Modified 响应,并且不会在不必要时下载数据。

  • 如果存储桶中的数据已更改,则世代编号不匹配,并且前提条件成功。这意味着整个请求会正常进行并下载内容的更新版本。

后续步骤