处理扩展程序的生命周期事件

您的扩展程序可以包含 Cloud Tasks 函数,在扩展程序实例发生以下任一生命周期事件时触发:

  • 有扩展程序实例被安装
  • 有扩展程序实例被更新到新版本
  • 有扩展程序实例的配置发生更改

此功能最重要的一个使用场景是回填数据。例如,假设您在构建一个扩展程序,用于为上传到 Cloud Storage 存储桶的图片生成缩略图预览。那么,这个扩展程序的主要工作将由一个通过 onFinalize Cloud Storage 事件触发的函数来完成。然而,只有在安装了扩展程序之后,上传的图片才会被处理。不过,您可以向该扩展程序添加一个由 onInstall 生命周期事件触发的函数,这样,在用户安装该扩展程序时,它也会为用户的所有现有图片生成缩略图预览。

下面是生命周期事件触发器的一些其他使用场景:

  • 自动执行安装后设置(创建数据库记录、编入索引等)
  • 若必须以不向后兼容的方式发布更改,可在更新时自动迁移数据

短时间运行的生命周期事件处理脚本

如果您的任务可以在 Cloud Functions 函数运行时长上限(若使用第一代 API,该时长上限为 9 分钟)内运行完毕,您可以将生命周期事件处理脚本编写成一个单独的函数,通过任务队列 onDispatch 事件来触发:

export const myTaskFunction = functions.tasks.taskQueue()
  .onDispatch(async () => {
    // Complete your lifecycle event handling task.
    // ...

    // When processing is complete, report status to the user (see below).
  });

然后,在扩展程序的 extension.yaml 文件中,执行以下操作:

  1. 将函数注册为扩展程序资源并添加 taskQueueTrigger 属性集。如果您将 taskQueueTrigger 设置为空映射 ({}),您的扩展程序将使用默认设置预配 Cloud Tasks 队列;您可以视需要调整这些设置

    resources:
      - name: myTaskFunction
        type: firebaseextensions.v1beta.function
        description: >-
          Describe the task performed when the function is triggered by a lifecycle
          event
        properties:
          location: ${LOCATION}
          taskQueueTrigger: {}
    
  2. 将函数注册为一个或多个生命周期事件的处理脚本:

    resources:
      - ...
    lifecycleEvents:
      onInstall:
        function: myTaskFunction
        processingMessage: Resizing your existing images
      onUpdate:
        function: myOtherTaskFunction
        processingMessage: Setting up your extension
      onConfigure:
        function: myOtherTaskFunction
        processingMessage: Setting up your extension
    
    

    您可以为以下任何事件注册函数:onInstallonUpdateonConfigure。所有这些事件都是可选事件。

  3. 建议:如果您的扩展程序无需运行处理任务便可正常工作,您可以添加一个用户配置的参数,让用户选择是否启用处理任务。

    例如,您可以添加下面的参数:

    params:
      - param: DO_BACKFILL
        label: Backfill existing images
        description: >
          Should existing, unresized images in the Storage bucket be resized as well?
        type: select
        options:
          - label: Yes
            value: true
          - label: No
            value: false
    

    之后,在您的函数中,若该参数被设为 false,则提前退出:

    export const myTaskFunction = functions.tasks.taskQueue()
      .onDispatch(async () => {
        if (!process.env.DO_BACKFILL) {
          await runtime.setProcessingState(
            "PROCESSING_COMPLETE",
            "Existing images were not resized."
          );
          return;
        }
        // Complete your lifecycle event handling task.
        // ...
      });
    

执行长时间运行的任务

如果您的任务无法在 Cloud Functions 函数运行时长上限内完成,可以将任务拆分为多个子任务,并使用 Admin SDK 的 TaskQueue.enqueue() 方法将作业加入队列,依次执行每项子任务。

例如,假设您想要回填 Cloud Firestore 数据。您可以使用查询游标将文档集合拆分为多个数据块。处理一个数据块后,向后推进起始偏移量并将另一个函数调用加入队列,如下所示:

import { getFirestore } from "firebase-admin/firestore";
import { getFunctions } from "firebase-admin/functions";

exports.backfilldata = functions.tasks.taskQueue().onDispatch(async (data) => {
  // When a lifecycle event triggers this function, it doesn't pass any data,
  // so an undefined offset indicates we're on our first invocation and should
  // start at offset 0. On subsequent invocations, we'll pass an explicit
  // offset.
  const offset = data["offset"] ?? 0;

  // Get a batch of documents, beginning at the offset.
  const snapshot = await getFirestore()
    .collection(process.env.COLLECTION_PATH)
    .startAt(offset)
    .limit(DOCS_PER_BACKFILL)
    .get();
  // Process each document in the batch.
  const processed = await Promise.allSettled(
    snapshot.docs.map(async (documentSnapshot) => {
      // Perform the processing.
    })
  );

  // If we processed a full batch, there are probably more documents to
  // process, so enqueue another invocation of this function, specifying
  // the offset to start with.
  //
  // If we processed less than a full batch, we're done.
  if (processed.length == DOCS_PER_BACKFILL) {
    const queue = getFunctions().taskQueue(
      "backfilldata",
      process.env.EXT_INSTANCE_ID
    );
    await queue.enqueue({
      offset: offset + DOCS_PER_BACKFILL,
    });
  } else {
      // Processing is complete. Report status to the user (see below).
  }
});

将函数添加到 extension.yaml(如上一部分所述)。

报告状态

当您的所有处理函数都运行完毕,无论是成功完成还是出现错误,您都可以使用 Admin SDK 的扩展程序运行时方法报告任务的状态。用户可以在 Firebase 控制台的扩展程序详情页面上查看此状态。

成功完成以及非严重错误

如需报告成功完成以及非严重错误(指的是该错误并未导致扩展程序无法正常工作),可以使用 Admin SDK 的 setProcessingState() 扩展程序运行时方法:

import { getExtensions } from "firebase-admin/extensions";

// ...

getExtensions().runtime().setProcessingState(processingState, message);

您可以设置以下状态:

非严重状态
PROCESSING_COMPLETE

用于报告任务成功完成。示例:

getExtensions().runtime().setProcessingState(
  "PROCESSING_COMPLETE",
  `Backfill complete. Successfully processed ${numSuccess} documents.`
);
PROCESSING_WARNING

用于报告部分成功。示例:

getExtensions().runtime().setProcessingState(
  "PROCESSING_WARNING",
  `Backfill complete. ${numSuccess} documents processed successfully.`
    + ` ${numFailed} documents failed to process. ${listOfErrors}.`
    + ` ${instructionsToFixTheProblem}`
);
PROCESSING_FAILED

用于报告阻止任务完成但并未导致扩展程序完全不可用的错误。示例:

getExtensions().runtime().setProcessingState(
  "PROCESSING_FAILED",
  `Backfill failed. ${errorMsg} ${optionalInstructionsToFixTheProblem}.`
);

如需报告确实会导致扩展程序完全不可用的错误,可以调用 setFatalError()

NONE

用于清除任务的状态。您可以视需要使用此字段清除控制台中的状态消息(例如,在设置 PROCESSING_COMPLETE 一定时间后)。 示例:

getExtensions().runtime().setProcessingState("NONE");

严重错误

如果发生了导致扩展程序无法正常运行的错误(例如,某项必须完成的设置任务失败),可使用 setFatalError() 报告该严重错误:

import { getExtensions } from "firebase-admin/extensions";

// ...

getExtensions().runtime().setFatalError(`Post-installation setup failed. ${errorMessage}`);

调整任务队列

如果您将 taskQueueTrigger 属性设置为 {},则您的扩展程序实例在安装时会使用默认设置预配 Cloud Tasks 队列。或者,您也可以提供特定值,调整任务队列的并发限制和重试行为:

resources:
  - name: myTaskFunction
    type: firebaseextensions.v1beta.function
    description: >-
      Perform a task when triggered by a lifecycle event
    properties:
      location: ${LOCATION}
      taskQueueTrigger:
        rateLimits:
          maxConcurrentDispatches: 1000
          maxDispatchesPerSecond: 500
        retryConfig:
          maxAttempts: 100  # Warning: setting this too low can prevent the function from running
          minBackoffSeconds: 0.1
          maxBackoffSeconds: 3600
          maxDoublings: 16
lifecycleEvents:
  onInstall: 
    function: myTaskFunction
    processingMessage: Resizing your existing images
  onUpdate:
    function: myTaskFunction
    processingMessage: Setting up your extension
  onConfigure:
    function: myOtherTaskFunction
    processingMessage: Setting up your extension

如需详细了解这些参数,请参阅 Google Cloud 文档中的配置 Cloud Tasks 队列部分。

请勿尝试通过将任务队列参数传递给 taskQueue() 来指定任务队列参数。系统会忽略这些设置,而采用 extension.yaml 中的配置以及配置默认值。

例如,以下方法不起作用:

export const myBrokenTaskFunction = functions.tasks
  // DON'T DO THIS IN AN EXTENSION! THESE SETTINGS ARE IGNORED.
  .taskQueue({
    retryConfig: {
      maxAttempts: 5,
      minBackoffSeconds: 60,
    },
    rateLimits: {
      maxConcurrentDispatches: 1000,
      maxDispatchesPerSecond: 10,
    },
  })
  .onDispatch(
    // ...
  );

extension.yaml 中的 taskQueueTrigger 属性是配置扩展程序的任务队列的唯一方法。

示例

官方的 storage-resize-imagesfirestore-bigquery-exportfirestore-translate-text 扩展程序都使用了生命周期事件处理脚本来回填数据。