世代编号和前提条件

对象世代编号使用户能够对数据资源进行唯一标识,并应用前提条件以保证多步骤事务的原子性。

世代

即使未启用对象版本控制,所有 Cloud Storage 对象仍会具有世代编号和元数据世代编号。每次覆盖对象时,世代编号都会发生更改;每次更新对象的元数据时,元数据世代编号也会发生更改。

对于每个新的对象世代,元数据世代编号会重置为 1,因此,仅当与世代编号结合到一起时,元数据世代编号才具有实际意义。

存储分区也会保留元数据世代编号,其目的是帮助用户对存储分区元数据状态进行唯一标识。存储分区没有负载数据(因此没有世代编号),在此情况下,它们的元数据世代编号本身具有实际意义。

示例:并行上传

并行上传中,您将对象分成多个部分,然后同时将各部分内容上传到一个临时位置,并通过这些临时生成的组成部分 compose 原始对象。如果一个独立的进程无意中使用了与您上传的一个或多个临时内容片段相同的名称,那么,当您尝试 compose 对象时,系统将使用不正确的组成部分来合成对象,导致该对象发生损坏。

通过使用世代编号,您可以防止发生此类损坏。如果您在发出 compose 请求时将每个上传片段的世代编号包含在内,则会出现两种情况:要么 compose 操作使用正确的片段来合成对象;要么请求失败并返回一则 404 Not Found 响应。

前提条件

前提条件向 Cloud Storage 告知,仅当受影响的对象的世代编号或元数据世代编号满足您的前提条件时,Cloud Storage 才应执行请求。通过检查世代编号和元数据世代编号,您可以确保对象处于预期的状态,这样一来,您就可以在对象上安全执行“读取-修改-写入”更新和条件化操作。

match 前提条件使用特定的世代编号或元数据世代编号时,请求所适用的 Cloud Storage 对象也必须具有相同的世代编号或元数据世代编号。如果满足上述条件,则请求成功。如果不满足上述条件,则请求失败并返回一则 412 Precondition Failed 响应。

如果 match 前提条件使用值 0(而不是世代编号),那么,仅当 Cloud Storage 存储分区中没有活动对象具有请求中所指定的名称时,请求才会成功。如果存在这样的对象,则请求将失败并返回一则 412 Precondition Failed 响应。

前提条件通常用在更改型请求中 - 上传、删除、复制或元数据更新 - 其目的是防止出现竞争条件。当同一请求被反复发送,或独立进程相互干扰时,可能会出现竞争条件。例如,在网络中断后多次重试请求,或者用户对同一对象执行读取-修改-写入操作,这些情况都可能会产生竞争条件。

除了那些使用世代编号和元数据世代编号的前提条件之外,我们还提供了使用 ETag 的前提条件。非复合对象的 XML API ETag 仅在内容更改时发生更改,而复合对象和 JSON API 资源的 ETag 会在内容或元数据更改时发生更改。要详细了解 ETag,请参阅哈希值和 ETag:最佳做法

前提条件的费用

使用前提条件时,会产生性能和结算费用:执行每个更改操作时,您也会发出一个 GET 元数据请求(可计费),以便确定对象的世代编号/元数据世代编号。在性能方面,您需要注意一点:前提条件会增加一次额外的往返过程,因此有可能导致网络延迟时间翻倍,进而导致总体操作延迟时间变长。对于那些对延迟时间敏感的操作,您需要着重考虑这一点。从价格方面考虑,允许您使用前提条件的 GET 元数据请求的结算费率为每 10,000 次操作 0.004 美元。

视您的应用而定,您可以通过某些方法来省下使用前提条件所产生的性能和结算费用,例如:

  • 在本地存储对象的世代编号和元数据世代编号,以便您提前知晓要在前提条件中使用的正确编号。
  • 使用适当的命名方案(避免对同一对象名称做出多次更改),使得您无需使用前提条件。
  • 让您的应用知晓哪些对象是新创建的,这样一来,您可以提前知道应在何时使用 if-generation-match:0 前提条件。
  • 记住更改之前执行的 GET 调用的结果。

XML API 中的前提条件

在 XML API 中,世代编号和元数据世代编号分别通过 x-goog-generationx-goog-metageneration 响应标头展示。对象的 HEAD 请求的响应中返回了这些标头。

如需查看前提条件请求标头(您可以使用这些标头,根据所请求对象的状态有条件地执行请求)的完整列表,请参阅 HTTP 标头参考。例如,您可以执行以下操作:

  • 使用 x-goog-if-generation-match 前提条件实现以下目的:仅当前提条件中的世代编号与请求对象的世代编号匹配时,才执行请求。如果使用 0(而不是世代编号),则只有当您的存储分区中没有活动对象与请求中指定的对象匹配时,请求才会成功。

  • 使用 x-goog-if-metageneration-match 前提条件实现以下目的:仅当标头中的元数据世代编号与请求对象的元数据世代编号匹配时,才执行请求。

  • If-Modified-Since 前提条件与 GETHEAD 请求结合使用。仅当对象的最新世代的创建时间(即对象的最后修改时间)晚于前提条件中指定的时间时,才会执行这些请求。

  • 将 ETag 以及 If-MatchIf-None-Match 前提条件与 GETHEAD 请求结合使用。根据条件,仅当请求的对象与前提条件中指定的 ETag 匹配或不匹配时,才会执行这些请求。

  • 在同一请求中使用多个前提条件。例如,如果您使用了 x-goog-if-generation-match,则还可以使用 x-goog-if-metageneration-match

JSON API 中的前提条件

在 JSON API 中,您可以通过对象存储分区资源位于其中的响应的 generationmetageneration 属性获取世代编号和元数据世代编号。对象存储分区GET 请求的响应正文中会返回对象或存储分区资源。

要在请求上使用前提条件,请将前提条件作为查询参数添加到网址的末尾。对于每个前提条件,请添加一个与前提条件同名的查询参数。例如,使用 ifGenerationMatch 的请求如下所示:https://www.googleapis.com/storage/v1/b/testgrid-triage-testing/o/test?ifGenerationMatch=1122334455

在此示例中,仅当对象的世代是 1122334455 时,API 才会执行此请求。

JSON API 还支持为所有资源(包括存储分区、对象和 ACL)使用 HTTP 1.1 ETag 和相应的 HTTP If-MatchIf-None-Match 标头。每当返回资源时,ETag 都会作为响应标头的一部分返回,并包含在资源中。

以下是一些前提条件的示例,您可以使用这些前提条件,根据所请求对象的状态有条件地执行请求:

  • 使用 ifGenerationMatchifGenerationNotMatch 前提条件实现以下目的:仅当属性中的世代编号与请求对象的世代编号匹配/不匹配时,才执行请求。如果在 ifGenerationMatch 使用 0(而不是世代编号),则只有当您的存储分区中没有活动对象与请求中指定的对象匹配时,请求才会成功。

  • 使用 ifMetagenerationMatchifMetagenerationNotMatch 前提条件在存储分区或对象上执行请求。仅当属性中的元数据世代编号与所请求的存储分区或对象的元数据世代编号匹配/不匹配时,请求才会成功。

  • 在请求中使用 ETag 以及 If-MatchIf-None-Match 前提条件作为标头。根据条件,仅当请求的对象与前提条件中指定的 ETag 匹配或不匹配时,才会执行这些请求。

  • 在同一请求中使用多个前提条件。例如,如果您在请求对象时使用了 ifGenerationMatch,则您也可以使用 ifMetagenerationMatch

请注意,不允许为 ACL 操作使用世代前提条件和元数据世代前提条件;请改为使用访问控制条目资源 ETag。您可以在每个访问控制条目资源中找到 Etag(也可以从包含该资源的对象或存储分区资源进行访问)。

竞争条件的示例

同时读取-修改-写入

在更新存储分区或对象元数据时,常见的模式包括以下几个步骤:读取当前状态、在本地应用修改、以及将修改后的元数据发回 Cloud Storage 进行写入。如果两个或更多个独立进程同时尝试执行此序列,此模式可能并不稳定。

请考虑以下情况:您希望为协作者添加一个 ACL 条目,以允许他们访问您的存储分区。同时,您的一位同事想要移除另一位不再需要访问存储分区的协作者。

为实现目的,您和您的同事都会读取存储分区元数据的同一初始状态,并且你们两都对存储分区的 ACL 条目做出所需的修改。当您将修改写回到 Cloud Storage 时,元数据会正确更新。不幸的是,一旦您的同事上传他的修改,您所做的更改就会丢失,因为他无法考虑到您也做出了更新。因此,您的协作者的 ACL 条目将丢失,她将无法访问您的存储分区,并且没有人知道发生了什么情况(除非您再次去检查 ACL 条目)。

防止出现竞争条件

为了阻止出现此竞争条件,您和您的同事可以为你们各自的写入操作添加 if-metageneration-match 前提条件。在前提条件中,你们两都将使用存储分区的元数据世代编号(此编号包含在您在初始读取操作中收到的元数据中)。

当您通过修改操作添加 ACL 条目时,存储分区的元数据世代编号会发生更改。使用前提条件后,当您的同事尝试写入他的 ACL 条目版本时,存储分区的元数据世代编号与前提条件中的编号不匹配,系统会向他告知更新失败并显示响应代码 412 Precondition Failed。收到此响应代码后,您的同事可以做出相应的反应,例如使用更新后的元数据执行新的读取-修改-写入循环。

多次重试请求

Cloud Storage 是一个分布式系统。由于请求可能会因网络或服务条件而失败,因此,Google 建议您使用指数回退重试请求。但是,由于分布式系统的性质,有时这些重试操作会导致出现令人惊讶的行为。

请考虑以下情况:您想要删除存储在 Cloud Storage 中的文件 file.txt。之后,您希望将具有相同名称的新文件添加到 Cloud Storage 中。

为了完成此操作,您发出删除请求以删除该对象。但是,网络条件(例如,中间路由器暂时失去连接)会阻止请求到达 Cloud Storage,因此您不会收到响应。

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

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

下图显示了上述过程:

防止出现竞争条件

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

由于类似的网络中断可能会导致上传请求(在您的删除请求之后发出)出现竞争条件,因此,通过向上传请求应用 if-generation-match:0,您可以避免很多此类情况。通过使用此前提条件,您可以确保重试上传的操作不会意外地将对象写入两次,因为前提条件规定,仅当该对象当前不存在任何世代时,请求才能成功。

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

if-generation-match:0 的限制

if-generation-match:0 不能在第一个对象被删除的情况下阻止对象创建操作发生两次,因为对象的缺失无法被唯一标识。请考虑以下情况,在此情况下,没有发生数据丢失,但您最终获得了一个意料之外的文件:

  1. 您首先对 file.txt 的元数据执行 GET 请求,以便查找它的世代编号。在响应中,您发现 file.txt 不存在。

  2. 知道这一点后,您发出一个请求(附带 if-generation-match:0 前提条件)以上传 file.txt,但是,当中间路由器暂时失去连接时,请求超时。

  3. 第一次失败后,您再次使用 if-generation-match:0 前提条件重试上传请求。这一次,请求取得成功。

  4. 不久之后,您发送一条删除 file.txt 的请求,该请求取得成功。

  5. 如果失去连接的路由器现在重新获得连接,并将您的第一个上传请求发送到 Cloud Storage,则请求附带的前提条件仍然存在匹配,因此,系统将重新创建 file.txt。无论是否存在前提条件,file.txt 都会意外地被再次上传。

此页内容是否有用?请给出您的反馈和评价:

发送以下问题的反馈:

此网页
Cloud Storage
需要帮助?请访问我们的支持页面