傳送批次要求

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

批次要求總覽

用戶端建立的每個 HTTP 連線都會造成一定程度的負擔。Cloud Storage JSON API 支援批次處理要求,可讓用戶端將多個 API 呼叫分組為單一 HTTP 要求。將多個 API 呼叫分組為單一要求,可減少網路負擔和延遲時間,方便您執行多項作業。

您可能想使用批次要求執行的作業範例:

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

Cloud Storage 不支援上傳或下載的批次作業。

批次要求結構

批次要求是單一標準 HTTP 要求,內含多個 Cloud Storage JSON API 呼叫。外部批次要求會在 Content-Type 要求標頭中使用 multipart/mixed,主體則由多個部分組成,每個部分都包含個別的 HTTP 子要求。

外層批次要求會傳送至批次端點:

https://storage.googleapis.com/batch/storage/v1

批次主體中的每個子要求都會指定標準的 Cloud Storage JSON API 要求路徑,例如:

https://storage.googleapis.com/storage/v1/BUCKET_NAME/o/OBJECT_NAME

如要查看批次要求範例的格式,請參閱「範例」。

為避免發生 HTTP 400 錯誤、閘道逾時和連線中斷等問題,單一批次要求不應包含超過 100 個呼叫。如需進行超過 100 個呼叫,請使用多個批次要求。批次要求酬載總大小不得超過 10 MB。Cloud Storage 會拒絕較大的要求。

批次要求格式

本節說明批次要求的格式。

在外部要求的主體中,每個部分都會以自己的 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 發出批次要求,請參閱 Cloud Storage Ruby API 參考文件

Rust

Rust 用戶端程式庫不支援批次要求。

批次 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 狀態碼。