处理长时间运行的操作

有时,API 可能会公开一个需要大量时间才能完成的方法。您可以返回一个 promise,让用户检查状态,而不是在任务运行时进行阻塞。

Rust 的 Google Cloud 客户端库提供了用于处理这些 长时间运行的操作 (LRO) 的帮助程序。本指南将向您介绍如何启动 LRO 并等待其完成。

前提条件

本指南使用 Cloud Storage 服务来保持代码段的具体性。 这些概念适用于使用 LRO 的任何其他服务。

在按照本指南操作之前,您应该:

如需查看 Rust 库的完整设置说明,请参阅 设置开发环境

依赖项

声明 Google Cloud 依赖项在您的 Cargo.toml 文件中:

cargo add google-cloud-storage google-cloud-lro google-cloud-longrunning

您还需要几个 tokio 功能:

cargo add tokio --features full,macros

启动长时间运行的操作

此示例使用重命名文件夹。对于大型文件夹,此操作可能需要很长时间,但对于较小的文件夹,此操作相对较快。

如需启动长时间运行的操作,您必须初始化客户端并执行 RPC。

首先,添加 use 声明以避免使用过长的软件包名称:

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

接下来,创建客户端:

let client = StorageControl::builder().build().await?;

在 Rust 客户端库中,每个请求都由一个返回请求构建器的方法表示。在客户端上调用该方法以创建请求构建器:

let operation = client
    .rename_folder()
    .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
    .set_destination_folder_id(dest)

示例函数接受存储桶和文件夹名称作为实参:

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {

发出请求并等待返回 Operation 。此 Operation 充当长时间运行的请求结果的 promise:

    let operation =
        // ...
        .send()
        .await?;

此请求会在后台启动操作。等待操作完成,以确定操作是否成功。

自动轮询长时间运行的操作

如需配置自动轮询,您将 启动长时间运行的操作,使用 a Poller 而不是 .send().wait,如下所示:

.poller()
.until_done()
.await?;

首先,使用 use 声明在范围内引入 Poller 特征:

use google_cloud_lro::Poller;

然后,像之前一样初始化客户端并准备请求:

let response = client
    .rename_folder()
    .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
    .set_destination_folder_id(dest)

轮询,直到操作完成并输出结果:

    .poller()
    .until_done()
    .await?;

println!("LRO completed, response={response:?}");

轮询具有中间结果的长时间运行的操作

.until_done() 方法很方便,但它会省略长时间运行的操作的部分进度报告。如果您的应用需要此信息,请直接使用轮询器:

    let mut poller = client
        .rename_folder()
        /* more stuff */
        .poller();

然后在循环中使用轮询器:

while let Some(p) = poller.poll().await {
    match p {
        PollingResult::Completed(r) => {
            println!("LRO completed, response={r:?}");
        }
        PollingResult::InProgress(m) => {
            println!("LRO in progress, metadata={m:?}");
        }
        PollingResult::PollingError(e) => {
            println!("Transient error polling the LRO: {e}");
        }
    }
    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}

此循环会在再次轮询之前显式等待。轮询周期取决于具体操作及其载荷。请参阅服务文档或使用您的数据进行实验,以确定合适的值。

手动轮询长时间运行的操作

虽然我们建议使用自动轮询方法,但也可以手动轮询长时间运行的操作。如需了解详情,请参阅 Operation 消息参考文档。

使用客户端启动长时间运行的操作:

    let mut operation = client
        .rename_folder()
        /* more stuff */
        .send()
        .await?;

启动轮询循环,并使用 done 字段检查操作是否已完成:

let response: anyhow::Result<Folder> = loop {
    if operation.done {

操作完成后,通常会包含结果。结果字段是可选的,因为服务可能会返回 done 作为 true,而没有结果。例如,成功的删除操作没有返回值。在此示例中,Cloud Storage 服务始终返回一个值:

match &operation.result {
    None => {
        break Err(anyhow!("missing result for finished operation"));
    }

启动的操作可能无法成功完成。结果可能是错误或有效响应。首先检查是否存在错误:

Some(r) => {
    break match r {
        longrunning::model::operation::Result::Error(s) => {
            Err(anyhow!("operation completed with error {s:?}"))
        }

错误类型为 Status 消息类型。这不会实现标准 Error 特征。使用 Error::service 手动将其转换为有效错误。

如果结果成功,请提取响应类型。在 LRO 方法文档或服务 API 文档中查找此类型:

longrunning::model::operation::Result::Response(any) => {
    let response = any.to_msg::<Folder>()?;
    Ok(response)
}

请注意,如果类型与服务发送的内容不匹配,则提取值可能会失败。

Google Cloud 类型可能会在未来添加字段和分支。Rust 的客户端库将所有结构体和枚举标记为 #[non_exhaustive]。 Google Cloud处理这种情况:

_ => Err(anyhow!("unexpected result branch {r:?}")),

如果操作尚未完成,则可能包含元数据。某些服务包含有关请求的初始信息,而其他服务包含部分进度报告。您可以提取并报告此元数据:

if let Some(any) = &operation.metadata {
    let metadata = any.to_msg::<RenameFolderMetadata>()?;
    println!("LRO in progress, metadata={metadata:?}");
}

等待一段时间,然后再进行轮询。考虑使用 截断指数退避算法调整轮询周期。此示例每 500 毫秒轮询一次:

tokio::time::sleep(std::time::Duration::from_millis(500)).await;

查询操作状态:

if let Ok(attempt) = client
    .get_operation()
    .set_name(&operation.name)
    .send()
    .await
{
    operation = attempt;
}

为简单起见,此示例会忽略所有错误。在您的应用中,您可以将一部分错误视为不可恢复的错误,并限制轮询尝试次数。

后续步骤

  • GitHub 上查看示例的源代码。