傳送批次要求

本文說明如何批次處理 JSON API 呼叫,以減少用戶端存取 Cloud Storage 時必須建立的 HTTP 連線數量。

總覽

用戶端建立的每個 HTTP 連線都會造成一定程度的負擔。Cloud Storage JSON API 支援批次作業,可以讓用戶端在單一 HTTP 要求中加入數個 API 呼叫。

以下是您可能想要使用批次作業的狀況範例:

  • 更新多個物件的中繼資料,例如權限。
  • 刪除大量物件。

在上述任一情況中,您都可以將這些呼叫分組為單一 HTTP 要求,而不必個別傳送。所有內部要求都必須傳送至 Cloud Storage JSON API。

單一批次要求最多只能包含 100 個呼叫。如果您需要進行更多呼叫,請使用多個批次要求。批次要求酬載總大小不得超過 10 MB。

批次詳細資料

批次要求是由多個 API 呼叫合併成一個 HTTP 要求,系統會將這個要求傳送至 Cloud Storage 批次端點 (https://storage.googleapis.com/batch/storage/v1)。本節詳細說明批次語法,並在後半段提供範例

批次要求的格式

批次要求是單一標準 HTTP 要求,內含多個 Cloud Storage JSON API 呼叫。這項主要要求會使用 multipart/mixed 內容類型。在主要 HTTP 要求中,有多個部分,每個部分都含有一個巢狀的 HTTP 要求。

每個部分都以自己的 Content-Type: application/http HTTP 標頭開頭。 這個部分也可以視需要加上 Content-ID 標頭。這些標頭會標示部分的開頭,但與巢狀 HTTP 要求分開。也就是說,伺服器將批次要求解壓縮為個別要求後,就會忽略部分標頭。

每個部分的主體本身都是完整的 HTTP 要求,具有自己的動詞、網址、標頭和主體。這些 HTTP 要求只能包含網址的路徑部分,完整網址可能會導致未定義的行為。

外部批次要求的 HTTP 標頭 (Content- 標頭除外,例如 Content-Type) 也適用於每個巢狀要求。不過,如果您在外部要求和巢狀要求中都指定了特定 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.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageBatch;
import com.google.cloud.storage.StorageBatchResult;
import com.google.cloud.storage.StorageException;
import com.google.cloud.storage.StorageOptions;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class BatchSetObjectMetadata {
  public static void batchSetObjectMetadata(
      String projectId, String bucketName, String pathPrefix) {
    // 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 pathPrefix = "yourPath/";

    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(pathPrefix),
            Storage.BlobListOption.delimiter("/"));
    StorageBatch batchRequest = storage.batch();

    // Add all blobs with the given prefix to the batch request
    List<StorageBatchResult<Blob>> batchResults =
        blobs
            .streamAll()
            .map(blob -> batchRequest.update(blob.toBuilder().setMetadata(newMetadata).build()))
            .collect(Collectors.toList());

    // Execute the batch request
    batchRequest.submit();
    List<StorageException> failures =
        batchResults.stream()
            .map(
                r -> {
                  try {
                    BlobInfo blob = r.get();
                    return null;
                  } catch (StorageException e) {
                    return e;
                  }
                })
            .filter(Objects::nonNull)
            .collect(Collectors.toList());

    System.out.println(
        (batchResults.size() - failures.size())
            + " blobs in bucket "
            + bucketName
            + " with prefix '"
            + pathPrefix
            + "' had their metadata updated successfully.");

    if (!failures.isEmpty()) {
      System.out.println("While processing, there were " + failures.size() + " failures");

      for (StorageException failure : failures) {
        failure.printStackTrace(System.out);
      }
    }
  }
}

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 狀態碼,回應會包含每個子要求的回應,包括每個子要求的狀態碼,指出子要求成功或失敗。舉例來說,批次刪除物件時,每個成功的子要求都會包含 204 No Content 狀態碼。