在 Cloud Storage 存储桶之间传输数据

Storage Transfer Service 可用于在同一 Google Cloud 项目内或不同项目之间的 Cloud Storage 存储桶之间转移大量数据。

存储桶迁移在许多情况下都非常有用。可通过迁移整合不同项目中的数据,将数据移至备份位置,或更改数据的位置

何时使用 Storage Transfer Service

Google Cloud 提供了多种在 Cloud Storage 存储桶之间转移数据的方法。我们建议您遵循以下准则:

  • 小于 1 TB 的转移:请使用 gsutilgcloud。有关说明,请参阅移动和重命名存储桶

  • 超过 1 TB 的转移:请使用 Storage Transfer Service。Storage Transfer Service 是一种代管式转移方法,以开箱即用的方式提供安全性、可靠性和高性能。这种方法无需优化和维护脚本,也无需处理重试。

本指南介绍使用 Storage Transfer Service 在 Cloud Storage 存储桶之间转移数据的最佳实践。

定义转移策略

转移策略取决于您的情况的复杂性。请务必在您的方案中考量以下注意事项。

选择存储桶名称

如需将数据移动到位于不同位置的存储桶,请选择以下方法之一:

  • 新存储桶名称。更新您的应用,使其指向使用其他名称的存储桶。
  • 保留存储桶名称。替换存储桶以保留当前名称,这意味着您无需更新应用。

在这两种情况下,您都应安排停机,并通知用户将会发生停机。请查看以下说明,了解哪种方案最适合您。

新存储桶名称

如果使用新存储桶名称,您需要更新使用当前存储桶的所有代码和服务。具体的操作方法取决于应用的构建和部署方式。

对于某些设置,此方法可能具有较短的停机时间,但需要执行更多工作来确保顺利过渡。这涉及以下步骤:

  1. 将数据复制到新存储桶。
  2. 开始停机。
  3. 更新应用以指向新存储桶。
  4. 验证一切都按预期运行,并且所有相关系统和账号都可以访问该存储桶。
  5. 删除原始存储桶。
  6. 结束停机。

保留存储桶名称

如果您不想将代码更改为指向新的存储桶名称,请使用此方法。这涉及以下步骤:

  1. 将数据复制到临时存储桶。
  2. 开始停机。
  3. 删除原始存储桶。
  4. 创建一个与原始存储桶同名的新存储桶。
  5. 将数据从临时存储桶复制到新存储桶。
  6. 删除临时存储桶。
  7. 验证一切都按预期运行,并且所有相关系统和账号都可以访问该存储桶。
  8. 结束停机。

最大限度地减少停机时间

转移期间,Storage Transfer Service 不会锁定对源或目标存储桶的读取或写入。

如果您选择在存储桶上手动锁定读/写,则可以按照以下两个步骤传输数据以最大限度地减少停机时间:播种和同步。

  1. 播种转移:执行批量转移,不锁定来源的读写权限。

  2. 同步转移:第一个作业完成后,锁定源存储桶的读写,然后执行另一个转移作业。Storage Transfer Service 默认为增量转移,因此第二个转移作业将仅转移播种转移期间更改的数据。

优化转移速度

在估算转移作业所需的时间时,请考虑可能的瓶颈。例如,如果来源包含数十亿个小文件,则转移速度将受限于 QPS。如果对象较大,那么带宽可能成为瓶颈。

带宽限制在区域级别设置,并在所有项目中公平地分配。如果有足够的带宽可用,Storage Transfer Service 每秒可以为每个转移作业完成大约 1,000 个任务。在这种情况下,您可以将作业拆分为多个较小的转移作业(例如通过包含和排除前缀来转移特定文件)来加快转移速度。

如果位置、存储类别和加密密钥都相同,则 Storage Transfer Service 不会创建字节的新副本;而是会创建指向源 blob 的新元数据条目。因此,大型资料库的相同位置和类别副本会很快完成,并且仅受限于 QPS。

删除操作也只是元数据操作。对于此类转移作业,可将其拆分成多个较小的作业以并行执行转移,从而提高速度。

保留元数据

使用 Storage Transfer Service 在 Cloud Storage 存储桶之间转移数据时,系统会保留以下对象元数据:

  • 用户创建的自定义元数据
  • Cloud Storage 固定键元数据字段,例如 Cache-Control、Content-Disposition、Content-Type 和 Custom-Time。
  • 对象大小。
  • 世代编号会作为 x-goog-reserved-source-generation 键的一个自定义元数据字段保留下来,您可以在以后修改或移除该字段。

使用 API 进行转移时,可以选择保留以下元数据字段:

  • ACL (acl)
  • 存储类别 (storageClass)
  • CMEK (kmsKey)
  • 临时冻结 (temporaryHold)
  • 对象创建时间 (customTime)

如需了解详情,请参阅 TransferSpec API 参考文档

以下元数据字段不会保留:

  • 上次更新时间 (updated)
  • etag
  • componentCount

如果保留该属性,对象创建时间将作为自定义字段 customTime 进行存储。对象的 updated 时间会在转移后重置,因此对象在存储类别中存在的时间也会重置。这意味着转移后,Coldline Storage 中的对象必须在目标位置再次存在 90 天,才能避免产生提前删除费用。

您可以使用 customTime 应用基于 createTime 的生命周期政策。现有的 customTime 值会被覆盖。

如需详细了解保留和不保留的元数据,请参阅元数据保留

处理有版本控制的对象

如果要转移存储对象的所有版本(而不仅仅是最新版本),您需要使用 gcloud CLI 或 REST API 来转移数据,同时结合使用 Storage Transfer Service 的清单功能

如需转移所有对象版本,请执行以下操作:

  1. 列出存储桶对象并将其复制到 JSON 文件中:

    gcloud storage ls --all-versions --recursive --json [SOURCE_BUCKET] > object-listing.json
    

    此命令通常每秒可列出约 1,000 个对象。

  2. 将 JSON 文件拆分为两个 CSV 文件,一个文件包含非当前版本,另一个文件包含当前版本:

    jq -r '.[] | select( .type=="cloud_object" and (.metadata | has("timeDeleted") | not)) | [.metadata.name, .metadata.generation] | @csv' object-listing.json > live-object-manifest.csv
    jq -r '.[] | select( .type=="cloud_object" and (.metadata | has("timeDeleted"))) | [.metadata.name, .metadata.generation] | @csv' object-listing.json > non-current-object-manifest.csv
    
  3. 在目标存储桶上启用对象版本控制

  4. 首先通过将 non-current-object-manifest.csv 清单文件传递为 transferManifest 字段的值来转移非当前版本。

  5. 然后,以相同的方式转移当前版本,即将 live-object-manifest.csv 指定为清单文件。

配置转移选项

设置转移作业时,您可以使用的一些选项如下:

  • Logging:Cloud Logging 提供各个对象的详细日志,让您可以验证转移状态并执行其他数据完整性检查。

  • 过滤:您可以通过包含和排除前缀来限制 Storage Transfer Service 要处理的对象。此选项可用于将一个转移作业拆分为多个转移作业,以便并行运行这些作业。如需了解详情,请参阅优化转移速度

  • 转移选项:您可以将转移作业配置为覆盖目标存储桶中的现有项;在目标存储桶中删除转移作业集中不存在的对象;或者从源存储桶中删除已转移的对象。

转移您的数据

定义转移策略后,您就可以执行转移本身。

创建新存储桶

在开始转移之前,请先创建存储桶。请参阅location_considerations,了解如何选择适当的存储桶位置。

创建新的存储桶时,您可能想要复制某些存储桶元数据。请参阅获取存储桶元数据,了解如何显示源存储桶的元数据,以便您可以将相同的设置应用到您的新存储桶。

将对象复制到新存储桶

您可以使用 Google Cloud 控制台、gcloud CLI、REST API 或客户端库将对象从源存储桶复制到新存储桶。您选择的方法取决于您的转移策略

以下说明适用于将对象从一个存储桶转移到另一个存储桶的基本用例,使用时应根据您的需求进行修改。

请勿在转移作业名称中包含敏感信息,例如个人身份信息 (PII) 或安全数据。资源名称可能会传播到其他 Google Cloud 资源的名称,并且可能会向您项目之外的 Google 内部系统公开。

Google Cloud 控制台

使用 Google Cloud 控制台中的 Cloud Storage Transfer Service

  1. 在 Google Cloud 控制台中打开“转移”页面。

    打开转移页面

  2. 点击创建转移作业
  3. 按照分步演示操作,在完成每个步骤后点击下一步

    • 开始:将 Google Cloud Storage 用作来源类型目标类型

    • 选择来源:直接输入所需存储桶的名称,或点击浏览查找并选择您需要的存储桶。

    • 选择目标位置:直接输入所需存储桶的名称,或点击浏览查找并选择您需要的存储桶。

    • 选择设置:选择转移后删除来源位置的文件选项。

    • 时间安排选项:您可以忽略此部分。

  4. 完成分步演示后,点击创建

    然后系统会开始将旧存储桶中的对象复制到新存储桶中。此过程可能需要一些时间;但是,点击创建后,您可以离开 Google Cloud 控制台。

    如需查看转移进度,请执行以下操作:

    在 Google Cloud 控制台中打开“转移”页面。

    打开转移页面

    如需了解如何在 Google Cloud 控制台中获取有关失败的 Storage Transfer Service 操作的详细错误信息,请参阅问题排查

  5. 如果您在设置过程中选择了在转移完成后删除来源对象复选框,则在转移操作完成后,无需执行任何操作来删除旧存储桶中的对象。但是,建议您删除旧的存储桶,此操作必须单独执行。

gcloud CLI

安装 gcloud CLI

安装 gcloud 命令行工具(如果尚未安装)。

然后,调用 gcloud init 以初始化该工具并指定项目 ID 和用户账号。如需了解详情,请参阅初始化 Cloud SDK

gcloud init

将服务账号添加到目标文件夹

您必须先将 Storage Transfer Service 服务账号添加到目标存储桶,然后才能创建转移作业。为此,请使用 gsutil iam ch

gsutil iam ch serviceAccount:project-12345678@storage-transfer-service.iam.gserviceaccount.com:roles/storage.admin gs://bucket_name

如需了解如何使用 Google Cloud 控制台或 API,请参阅 Cloud Storage 文档中的使用 IAM 权限

创建转移作业

如需创建新的转移作业,请使用 gcloud transfer jobs create 命令。除非指定了时间表或 --do-not-run,否则创建新作业时会启动指定的转移作业。

gcloud transfer jobs create SOURCE DESTINATION

其中:

  • SOURCE 是此转移作业的数据源,格式为 gs://BUCKET_NAME

  • DESTINATION 是您的新存储桶,格式为 gs://BUCKET_NAME

其他选项包括:

  • 作业信息:您可以指定 --name--description

  • 时间表:您可以指定 --schedule-starts--schedule-repeats-every--schedule-repeats-until--do-not-run

  • 对象条件:您可以使用条件确定要转移的对象。这些条件包括 --include-prefixes--exclude-prefixes 以及 --include-modified-[before | after]-[absolute | relative] 中基于时间的条件。

  • 转移选项:指定是否覆盖目标文件(--overwrite-when=differentalways),以及是否要在转移过程中或之后删除某些文件(--delete-from=destination-if-uniquesource-after-transfer);指定 [要保留的元数据值] 元数据;(可选)为转移的对象设置存储类别 (--custom-storage-class)。

  • 通知:使用 --notification-pubsub-topic--notification-event-types--notification-payload-format 为转移作业配置 Pub/Sub 通知

如需查看所有选项,请运行 gcloud transfer jobs create --help

例如,如需转移前缀为 folder1 的所有对象,请运行以下命令:

gcloud transfer jobs create gs://old-bucket gs://new-bucket \
  --include-prefixes="folder1/"

REST

本示例演示了如何将文件从一个 Cloud Storage 存储桶传输到另一个 Cloud Storage 存储桶。例如,您可以将数据移动到位于另一个位置的存储桶。

使用 transferJobs create 发出请求:

POST https://storagetransfer.googleapis.com/v1/transferJobs
{
  "description": "YOUR DESCRIPTION",
  "status": "ENABLED",
  "projectId": "PROJECT_ID",
  "schedule": {
      "scheduleStartDate": {
          "day": 1,
          "month": 1,
          "year": 2025
      },
      "startTimeOfDay": {
          "hours": 1,
          "minutes": 1
      },
      "scheduleEndDate": {
          "day": 1,
          "month": 1,
          "year": 2025
      }
  },
  "transferSpec": {
      "gcsDataSource": {
          "bucketName": "GCS_SOURCE_NAME"
      },
      "gcsDataSink": {
          "bucketName": "GCS_SINK_NAME"
      },
      "transferOptions": {
          "deleteObjectsFromSourceAfterTransfer": true
      }
  }
}

响应:

200 OK
{
  "transferJob": [
      {
          "creationTime": "2015-01-01T01:01:00.000000000Z",
          "description": "YOUR DESCRIPTION",
          "name": "transferJobs/JOB_ID",
          "status": "ENABLED",
          "lastModificationTime": "2015-01-01T01:01:00.000000000Z",
          "projectId": "PROJECT_ID",
          "schedule": {
              "scheduleStartDate": {
                  "day": 1,
                  "month": 1,
                  "year": 2015
              },
              "startTimeOfDay": {
                  "hours": 1,
                  "minutes": 1
              }
          },
          "transferSpec": {
              "gcsDataSource": {
                  "bucketName": "GCS_SOURCE_NAME",
              },
              "gcsDataSink": {
                  "bucketName": "GCS_NEARLINE_SINK_NAME"
              },
              "objectConditions": {
                  "minTimeElapsedSinceLastModification": "2592000.000s"
              },
              "transferOptions": {
                  "deleteObjectsFromSourceAfterTransfer": true
              }
          }
      }
  ]
}

客户端库

本示例演示了如何将文件从一个 Cloud Storage 存储桶传输到另一个 Cloud Storage 存储桶。例如,您可以将数据复制到位于另一个位置的存储桶。

如需详细了解 Storage Transfer Service 客户端库,请参阅 Storage Transfer Service 客户端库使用入门

Java

想要寻找较早的示例?请参阅 Storage Transfer Service 迁移指南

import com.google.protobuf.Duration;
import com.google.storagetransfer.v1.proto.StorageTransferServiceClient;
import com.google.storagetransfer.v1.proto.TransferProto.CreateTransferJobRequest;
import com.google.storagetransfer.v1.proto.TransferTypes.GcsData;
import com.google.storagetransfer.v1.proto.TransferTypes.ObjectConditions;
import com.google.storagetransfer.v1.proto.TransferTypes.Schedule;
import com.google.storagetransfer.v1.proto.TransferTypes.TransferJob;
import com.google.storagetransfer.v1.proto.TransferTypes.TransferJob.Status;
import com.google.storagetransfer.v1.proto.TransferTypes.TransferOptions;
import com.google.storagetransfer.v1.proto.TransferTypes.TransferSpec;
import com.google.type.Date;
import com.google.type.TimeOfDay;
import java.io.IOException;
import java.util.Calendar;

public class TransferToNearline {
  /**
   * Creates a one-off transfer job that transfers objects in a standard GCS bucket that are more
   * than 30 days old to a Nearline GCS bucket.
   */
  public static void transferToNearline(
      String projectId,
      String jobDescription,
      String gcsSourceBucket,
      String gcsNearlineSinkBucket,
      long startDateTime)
      throws IOException {

    // Your Google Cloud Project ID
    // String projectId = "your-project-id";

    // A short description of this job
    // String jobDescription = "Sample transfer job of old objects to a Nearline GCS bucket.";

    // The name of the source GCS bucket to transfer data from
    // String gcsSourceBucket = "your-gcs-source-bucket";

    // The name of the Nearline GCS bucket to transfer old objects to
    // String gcsSinkBucket = "your-nearline-gcs-bucket";

    // What day and time in UTC to start the transfer, expressed as an epoch date timestamp.
    // If this is in the past relative to when the job is created, it will run the next day.
    // long startDateTime =
    //     new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2000-01-01 00:00:00").getTime();

    // Parse epoch timestamp into the model classes
    Calendar startCalendar = Calendar.getInstance();
    startCalendar.setTimeInMillis(startDateTime);
    // Note that this is a Date from the model class package, not a java.util.Date
    Date date =
        Date.newBuilder()
            .setYear(startCalendar.get(Calendar.YEAR))
            .setMonth(startCalendar.get(Calendar.MONTH) + 1)
            .setDay(startCalendar.get(Calendar.DAY_OF_MONTH))
            .build();
    TimeOfDay time =
        TimeOfDay.newBuilder()
            .setHours(startCalendar.get(Calendar.HOUR_OF_DAY))
            .setMinutes(startCalendar.get(Calendar.MINUTE))
            .setSeconds(startCalendar.get(Calendar.SECOND))
            .build();

    TransferJob transferJob =
        TransferJob.newBuilder()
            .setDescription(jobDescription)
            .setProjectId(projectId)
            .setTransferSpec(
                TransferSpec.newBuilder()
                    .setGcsDataSource(GcsData.newBuilder().setBucketName(gcsSourceBucket))
                    .setGcsDataSink(GcsData.newBuilder().setBucketName(gcsNearlineSinkBucket))
                    .setObjectConditions(
                        ObjectConditions.newBuilder()
                            .setMinTimeElapsedSinceLastModification(
                                Duration.newBuilder().setSeconds(2592000 /* 30 days */)))
                    .setTransferOptions(
                        TransferOptions.newBuilder().setDeleteObjectsFromSourceAfterTransfer(true)))
            .setSchedule(Schedule.newBuilder().setScheduleStartDate(date).setStartTimeOfDay(time))
            .setStatus(Status.ENABLED)
            .build();

    // Create a Transfer Service client
    StorageTransferServiceClient storageTransfer = StorageTransferServiceClient.create();

    // Create the transfer job
    TransferJob response =
        storageTransfer.createTransferJob(
            CreateTransferJobRequest.newBuilder().setTransferJob(transferJob).build());

    System.out.println("Created transfer job from standard bucket to Nearline bucket:");
    System.out.println(response.toString());
  }
}

Python

想要寻找较早的示例?请参阅 Storage Transfer Service 迁移指南

from datetime import datetime

from google.cloud import storage_transfer
from google.protobuf.duration_pb2 import Duration

def create_daily_nearline_30_day_migration(
    project_id: str,
    description: str,
    source_bucket: str,
    sink_bucket: str,
    start_date: datetime,
):
    """Create a daily migration from a GCS bucket to a Nearline GCS bucket
    for objects untouched for 30 days."""

    client = storage_transfer.StorageTransferServiceClient()

    # The ID of the Google Cloud Platform Project that owns the job
    # project_id = 'my-project-id'

    # A useful description for your transfer job
    # description = 'My transfer job'

    # Google Cloud Storage source bucket name
    # source_bucket = 'my-gcs-source-bucket'

    # Google Cloud Storage destination bucket name
    # sink_bucket = 'my-gcs-destination-bucket'

    transfer_job_request = storage_transfer.CreateTransferJobRequest(
        {
            "transfer_job": {
                "project_id": project_id,
                "description": description,
                "status": storage_transfer.TransferJob.Status.ENABLED,
                "schedule": {
                    "schedule_start_date": {
                        "day": start_date.day,
                        "month": start_date.month,
                        "year": start_date.year,
                    }
                },
                "transfer_spec": {
                    "gcs_data_source": {
                        "bucket_name": source_bucket,
                    },
                    "gcs_data_sink": {
                        "bucket_name": sink_bucket,
                    },
                    "object_conditions": {
                        "min_time_elapsed_since_last_modification": Duration(
                            seconds=2592000  # 30 days
                        )
                    },
                    "transfer_options": {
                        "delete_objects_from_source_after_transfer": True
                    },
                },
            }
        }
    )

    result = client.create_transfer_job(transfer_job_request)
    print(f"Created transferJob: {result.name}")

验证复制的对象

转移完成后,建议您执行其他数据完整性检查。

  • 验证对象上的元数据(例如校验和及大小),以验证对象已正确复制。

  • 验证是否已复制正确版本的对象。Storage Transfer Service 提供了一个开箱即用的选项,可用于验证是否已复制对象。如果您启用了日志记录,请查看日志以验证是否已成功复制所有对象,包括其相应的元数据字段。

开始使用目标存储桶

完成迁移并进行验证后,更新所有现有应用或工作负载,以使其使用目标存储桶名称。检查 Cloud Audit Logs 中的数据访问日志,以确保您的操作正确修改和读取对象。

删除原始存储桶

验证一切正常后,便可删除原始存储桶

Storage Transfer Service 提供了一个选项,可用于在转移对象后将其删除,您可以在作业配置中指定 deleteObjectsFromSourceAfterTransfer: true,也可以在 Google Cloud 控制台中选择该选项。

安排对象删除

如需安排稍后删除对象,请结合使用已安排的转移作业deleteObjectsUniqueInSink = true 选项。

您应该将转移作业设置为将一个空存储桶转移到包含对象的存储桶。这会使 Storage Transfer Service 列出对象并开始删除对象。由于删除操作只是元数据操作,因此转移作业仅受限于 QPS。为了加快该过程,请将转移作业拆分为多个作业,每个作业对一组不同的前缀执行操作。

或者,您也可以使用 Google Cloud 提供的代管式 Cron 作业调度服务。如需了解详情,请参阅使用 Cloud Scheduler 安排 Google Cloud STS 转移作业