发送批量请求

本文档介绍了如何对 JSON API 调用进行集中批处理以减少客户端在访问 Cloud Storage 时必须建立的 HTTP 连接数量。

概览

客户端建立的每个 HTTP 连接都会产生一定的开销。Cloud Storage JSON API 支持批处理,这样您的客户端就可以将多次 API 调用组合为一个 HTTP 请求。

在以下示例情况下,您可能需要使用批处理:

  • 更新多个对象的元数据,例如权限。
  • 删除多个对象。

在上述每种情况下,您都可以将这些调用组合成一个 HTTP 请求,而不是单独发送每次调用。请注意,所有内部请求都必须发送到 Cloud Storage JSON API。

单个批处理请求中包含的调用不应超过 100 次。如果您需要的调用次数超过此值,请使用多个批处理请求。批处理请求总载荷必须小于 10MB。

批量详情

批量请求就是将多个 API 调用进行合并而形成的一个 HTTP 请求,您可以将此请求发送到 Cloud Storage 批量端点,即 https://storage.googleapis.com/batch/storage/v1。本部分详细介绍了批处理语法,随后还会提供一个示例

批量请求的格式

批处理请求是一个包含多个 Cloud Storage JSON API 调用的标准 HTTP 请求。此主请求使用 multipart/mixed 内容类型。主 HTTP 请求中包含多个部分,每个部分均包含一个嵌套的 HTTP 请求。

各个部分都以其自身的 Content-Type: application/http 标头开头。 各部分还可以有一个可选的 Content-ID 标头。这些标头用于标记各部分的开头,但它们与嵌套的 HTTP 请求无关。这意味着,在服务器将批处理请求拆分为单独的请求后,该部分的标头会被忽略。

各个部分的正文本身是一个完整的 HTTP 请求,各自有专用的动词、网址、标头和正文。这些 HTTP 请求只能包含网址的路径部分;完整网址可能会有未定义的行为。

外部批处理请求的 HTTP 标头还应用于每个嵌套请求,但 Content-Type 之类的 Content- 标头除外。但是,如果您在外部请求和嵌套请求中都指定了特定的 HTTP 标头,则嵌套请求的标头值会替换该特定请求的外部批量请求标头值。

例如,如果您为特定嵌套请求提供了 Authorization 标头,则该标头将仅应用于指定该标头的请求。如果您为外部请求提供了 Authorization 标头,则该标头将应用于所有嵌套请求,除非这些嵌套请求将其替换为自身的 Authorization 标头。

当 Cloud Storage 收到批处理请求时,会将外部请求的查询参数和标头(如果适用)应用于各部分,然后将各部分视作单独的 HTTP 请求进行处理。

响应批处理请求

Cloud Storage 响应是一条包含 multipart/mixed 内容类型的标准 HTTP 响应。该主响应的每个部分都是对批处理请求中一项请求的响应。响应的顺序与请求相同。

与请求中的所有部分一样,每个响应部分都包含一个完整的 HTTP 响应,包括状态代码、标头和正文。此外,和请求中的各部分一样,响应中的各部分均以 Content-Type 标头为前缀,用于标记各部分的开头。如需详细了解状态代码,请参阅 Cloud Storage JSON API 的 HTTP 状态和错误代码

如果请求的某个特定部分具有 Content-ID 标头,则响应的对应部分也会有相同的 Content-ID 标头。响应的 Content-ID 标头以 response- 开头,后接请求中使用的 Content-ID 值,如示例中所示。

示例

以下批处理示例更新了 example-bucket 中三个对象的自定义元数据

批量 HTTP 请求示例

HTTP

POST /batch/storage/v1 HTTP/1.1
Host: storage.googleapis.com
Content-Length: 960
Content-Type: multipart/mixed; boundary="===============7330845974216740156=="
Authorization: Bearer ya29.AHES6ZRVmB7fkLtd1XTmq6mo0S1wqZZi3-Lh_s-6Uw7p8vtgSwg

--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+1>

PATCH /storage/v1/b/example-bucket/o/obj1 HTTP/1.1
Content-Type: application/json
accept: application/json
content-length: 31

{"metadata": {"type": "tabby"}}
--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+2>

PATCH /storage/v1/b/example-bucket/o/obj2 HTTP/1.1
Content-Type: application/json
accept: application/json
content-length: 32

{"metadata": {"type": "tuxedo"}}
--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+3>

PATCH /storage/v1/b/example-bucket/o/obj3 HTTP/1.1
Content-Type: application/json
accept: application/json
content-length: 32

{"metadata": {"type": "calico"}}
--===============7330845974216740156==--

客户端库

C++

C++ 客户端库不支持批量请求。

C#

C# 客户端库不支持批量请求。

Go

Go 客户端库不支持批量请求。

Java

如需了解详情,请参阅 Cloud Storage Java API 参考文档

import com.google.api.gax.paging.Page;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageBatch;
import com.google.cloud.storage.StorageOptions;
import java.util.HashMap;
import java.util.Map;

public class BatchSetObjectMetadata {
  public static void batchSetObjectMetadata(
      String projectId, String bucketName, String directoryPrefix) {
    // The ID of your GCP project
    // String projectId = "your-project-id";

    // The ID of your GCS bucket
    // String bucketName = "your-unique-bucket-name";

    // The directory prefix. All objects in the bucket with this prefix will have their metadata
    // updated
    // String directoryPrefix = "yourDirectory/";

    Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService();
    Map<String, String> newMetadata = new HashMap<>();
    newMetadata.put("keyToAddOrUpdate", "value");
    Page<Blob> blobs =
        storage.list(
            bucketName,
            Storage.BlobListOption.prefix(directoryPrefix),
            Storage.BlobListOption.currentDirectory());
    StorageBatch batchRequest = storage.batch();

    // Add all blobs with the given prefix to the batch request
    for (Blob blob : blobs.iterateAll()) {
      batchRequest.update(blob.toBuilder().setMetadata(newMetadata).build());
    }

    // Execute the batch request
    batchRequest.submit();

    System.out.println(
        "All blobs in bucket "
            + bucketName
            + " with prefix '"
            + directoryPrefix
            + "' had their metadata updated.");
  }
}

Node.js

Node.js 客户端库不支持批量请求。

PHP

PHP 客户端库不支持批量请求。

Python

如需了解详情,请参阅 Cloud Storage Python API 参考文档


from google.cloud import storage

def batch_request(bucket_name, prefix=None):
    """
    Use a batch request to patch a list of objects with the given prefix in a bucket.

    Note that Cloud Storage does not support batch operations for uploading or downloading.
    Additionally, the current batch design does not support library methods whose return values
    depend on the response payload.
    See https://cloud.google.com/python/docs/reference/storage/latest/google.cloud.storage.batch
    """
    # The ID of your GCS bucket
    # bucket_name = "my-bucket"
    # The prefix of the object paths
    # prefix = "directory-prefix/"

    client = storage.Client()
    bucket = client.bucket(bucket_name)

    # Accumulate in a list the objects with a given prefix.
    blobs_to_patch = [blob for blob in bucket.list_blobs(prefix=prefix)]

    # Use a batch context manager to edit metadata in the list of blobs.
    # The batch request is sent out when the context manager closes.
    # No more than 100 calls should be included in a single batch request.
    with client.batch():
        for blob in blobs_to_patch:
            metadata = {"your-metadata-key": "your-metadata-value"}
            blob.metadata = metadata
            blob.patch()

    print(
        f"Batch request edited metadata for all objects with the given prefix in {bucket.name}."
    )

Ruby

如需了解如何使用 Ruby 发出批量请求,请参阅 Cloud Storage Ruby API 参考文档

批量 HTTP 响应示例

此部分是对上一部分中的示例 HTTP 请求的响应。

HTTP/1.1 200 OK
Content-Type: multipart/mixed; boundary=batch_pK7JBAk73-E=_AA5eFwv4m2Q=
Date: Mon, 22 Jan 2018 18:56:00 GMT
Expires: Mon, 22 Jan 2018 18:56:00 GMT
Cache-Control: private, max-age=0
Content-Length: 3767

--batch_pK7JBAk73-E=_AA5eFwv4m2Q=
Content-Type: application/http
Content-ID: <response-b29c5de2-0db4-490b-b421-6a51b598bd22+1>

HTTP/1.1 200 OK
ETag: "lGaP-E0memYDumK16YuUDM_6Gf0/V43j6azD55CPRGb9b6uytDYl61Y"
Content-Type: application/json; charset=UTF-8
Date: Mon, 22 Jan 2018 18:56:00 GMT
Expires: Mon, 22 Jan 2018 18:56:00 GMT
Cache-Control: private, max-age=0
Content-Length: 846

{
 "kind": "storage#object",
 "id": "example-bucket/obj1/1495822576643790",
 .
 .
 .
 "metadata": {
  "type": "tabby"
  },
  .
  .
  .
}

--batch_pK7JBAk73-E=_AA5eFwv4m2Q=
Content-Type: application/http
Content-ID: <response-b29c5de2-0db4-490b-b421-6a51b598bd22+2>

HTTP/1.1 200 OK
ETag: "lGaP-E0memYDumK16YuUDM_6Gf0/91POdd-sxSAkJnS8Dm7wMxBSDKk"
Content-Type: application/json; charset=UTF-8
Date: Mon, 22 Jan 2018 18:56:00 GMT
Expires: Mon, 22 Jan 2018 18:56:00 GMT
Cache-Control: private, max-age=0
Content-Length: 846

{
 "kind": "storage#object",
 "id": "example-bucket/obj2/1495822576643790",
 .
 .
 .
 "metadata": {
  "type": "tuxedo"
  },
  .
  .
  .
}

--batch_pK7JBAk73-E=_AA5eFwv4m2Q=
Content-Type: application/http
Content-ID: <response-b29c5de2-0db4-490b-b421-6a51b598bd22+3>

HTTP/1.1 200 OK
ETag: "lGaP-E0memYDumK16YuUDM_6Gf0/d2Z1F1_ZVbB1dC0YKM9rX5VAgIQ"
Content-Type: application/json; charset=UTF-8
Date: Mon, 22 Jan 2018 18:56:00 GMT
Expires: Mon, 22 Jan 2018 18:56:00 GMT
Cache-Control: private, max-age=0
Content-Length: 846

{
 "kind": "storage#object",
 "id": "example-bucket/obj3/1495822576643790",
 .
 .
 .
 "metadata": {
  "type": "calico"
  },
  .
  .
  .
}

--batch_pK7JBAk73-E=_AA5eFwv4m2Q=--

如果整体请求格式设置不正确,并且 Cloud Storage 无法解析为子请求,您会收到 400 错误。否则,即使部分或全部子请求失败,Cloud Storage 也会返回 200 状态代码。

如果整体请求返回 200 状态代码,则响应会包含每个子请求的结果,包括针对各子请求的状态码,指示子请求是成功还是失败。