使用 Cloud Tasks 將函式排入佇列

工作佇列函式會利用 Google Cloud Tasks,協助應用程式在主要應用程式流程之外,以非同步方式執行耗時、耗用資源或頻寬受限的工作。

舉例來說,假設您想備份大量圖片檔,而這些檔案目前託管於有頻率限制的 API。為了負責任地使用該 API,您必須遵守其頻率限制。此外,這類長時間執行的工作可能會因逾時和記憶體限制而失敗。

為降低複雜度,您可以編寫工作佇列函式,設定 scheduleTime 和 dispatchDeadline 等基本工作選項,然後將函式交給 Cloud Tasks 中的佇列。Cloud Tasks 環境經過特別設計,可確保這類作業的擁塞控制和重試政策有效。

Firebase SDK for Cloud Functions for Firebase 3.20.1 以上版本可與 Firebase Admin SDK 10.2.0 以上版本互通,支援工作佇列函式。

搭配 Firebase 使用工作佇列函式,可能會產生 Cloud Tasks 處理費用。詳情請參閱 Cloud Tasks 定價。

建立工作佇列函式

如要使用工作佇列函式,請按照下列工作流程操作:

  1. 使用 Cloud Functions 的 Firebase SDK 編寫工作佇列函式。
  2. 使用 HTTP 要求觸發函式,測試函式是否正常運作。
  3. 使用 Firebase CLI 部署函式。首次部署工作佇列函式時,CLI 會在 Cloud Tasks 中建立工作佇列,並根據原始碼中指定的選項 (頻率限制和重試) 進行設定。
  4. 將工作新增至新建立的工作佇列,並視需要傳遞參數來設定執行排程。如要這麼做,請使用 Admin SDK 編寫程式碼,並部署至 Cloud Functions for Firebase。

編寫工作佇列函式

本節中的程式碼範例是以應用程式為基礎,該應用程式會設定一項服務,備份 NASA 每日天文圖中的所有圖片。首先,請匯入必要模組:

Node.js

// Dependencies for task queue functions.
const {onTaskDispatched} = require("firebase-functions/tasks");
const {onRequest, HttpsError} = require("firebase-functions/https");
const {requiresRole} = require("firebase-functions");
const {getFunctions} = require("firebase-admin/functions");
const {logger} = require("firebase-functions");

// Dependencies for image backup.
const {URL, URLSearchParams} = require("node:url");
const path = require("path");
const {initializeApp} = require("firebase-admin/app");
const {getStorage} = require("firebase-admin/storage");

Python

# Dependencies for task queue functions.
from google.cloud import tasks_v2
import requests
from firebase_functions.options import RetryConfig, RateLimits, SupportedRegion

# Dependencies for image backup.
from datetime import datetime, timedelta
import json
import pathlib
from urllib.parse import urlparse
from firebase_admin import initialize_app, storage, functions
from firebase_functions import https_fn, tasks_fn, params
import google.auth
from google.auth.transport.requests import AuthorizedSession

請使用 onTaskDispatched 或 on_task_dispatched 執行工作佇列函式。編寫工作佇列函式時,您可以設定每個佇列的重試和頻率限制。

設定工作佇列函式

工作佇列函式提供一組強大的設定,可精確控制工作佇列的頻率限制和重試行為:

Node.js

exports.backupapod = onTaskDispatched(
    {
      retryConfig: {
        maxAttempts: 5,
        minBackoffSeconds: 60,
      },
      rateLimits: {
        maxConcurrentDispatches: 6,
      },
    }, async (req) => {

Python

@tasks_fn.on_task_dispatched(
    retry_config=RetryConfig(max_attempts=5, min_backoff_seconds=60),
    rate_limits=RateLimits(max_concurrent_dispatches=10),
)
def backupapod(req: tasks_fn.CallableRequest) -> str:
    """Grabs Astronomy Photo of the Day (APOD) using NASA's API."""
  • retryConfig.maxAttempts=5:工作佇列中的每項工作最多會自動重試 5 次。這有助於緩解暫時性錯誤,例如網路錯誤或依附的外部服務暫時中斷。

  • retryConfig.minBackoffSeconds=60:每項任務的重試間隔至少為 60 秒。這樣一來,每次嘗試之間就會有很大的緩衝時間,不會太快用完 5 次重試機會。

  • rateLimits.maxConcurrentDispatch=6:一次最多可調度 6 項工作。這有助於確保基礎函式能穩定接收要求,並減少執行中的執行個體數量和冷啟動次數。

測試工作佇列函式

在大多數情況下,Cloud Functions 模擬器是測試工作佇列函式的最佳方式。請參閱模擬器套件說明文件,瞭解如何檢測應用程式,以便模擬工作佇列函式。

此外,工作佇列函式在 Firebase Local Emulator Suite 中會公開為簡單的 HTTP 函式。如要測試模擬的工作函式,請傳送含有 JSON 資料酬載的 HTTP POST 要求:

 # start the Local Emulator Suite
 firebase emulators:start

 # trigger the emulated task queue function
 curl \
  -X POST                                            # An HTTP POST request...
  -H "content-type: application/json" \              # ... with a JSON body
  http://localhost:$PORT/$PROJECT_ID/$REGION/$NAME \ # ... to function url
  -d '{"data": { ... some data .... }}'              # ... with JSON encoded data

部署工作佇列函式

使用 Firebase CLI 部署工作佇列函式:

$ firebase deploy --only functions:backupapod

首次部署工作佇列函式時,CLI 會在 Cloud Tasks 中建立工作佇列,並根據原始碼中指定的選項 (頻率限制和重試) 進行設定。

如果在部署函式時遇到權限錯誤,請確認執行部署指令的使用者已獲派適當的 IAM 角色。

將工作佇列函式排入佇列

工作佇列函式可從 Cloud Functions for Firebase 等受信任的伺服器環境,使用 Node.js 的 Firebase Admin SDK 或 Python 的 Google Cloud 程式庫,在 Cloud Tasks 中加入佇列。如果您是 Admin SDK 新手,請參閱「將 Firebase 新增至伺服器」一文,瞭解如何開始使用。

一般流程會建立新工作、將工作排入 Cloud Tasks 佇列,並設定工作:

Node.js

exports.enqueuebackuptasks = onRequest(
    async (_request, response) => {
      const queue = getFunctions().taskQueue("backupapod");

      const enqueues = [];
      for (let i = 0; i <= BACKUP_COUNT; i += 1) {
        const iteration = Math.floor(i / HOURLY_BATCH_SIZE);
        // Delay each batch by N * hour
        const scheduleDelaySeconds = iteration * (60 * 60);

        const backupDate = new Date(BACKUP_START_DATE);
        backupDate.setDate(BACKUP_START_DATE.getDate() + i);
        // Extract just the date portion (YYYY-MM-DD) as string.
        const date = backupDate.toISOString().substring(0, 10);
        enqueues.push(
            queue.enqueue({date}, {
              scheduleDelaySeconds,
              dispatchDeadlineSeconds: 60 * 5, // 5 minutes
            }),
        );
      }
      await Promise.all(enqueues);
      response.sendStatus(200);
    });

Python

@https_fn.on_request()
def enqueuebackuptasks(_: https_fn.Request) -> https_fn.Response:
    """Adds backup tasks to a Cloud Tasks queue."""
    task_queue = functions.task_queue("backupapod")
    target_uri = get_function_url("backupapod")

    for i in range(BACKUP_COUNT):
        batch = i // HOURLY_BATCH_SIZE

        # Delay each batch by N hours
        schedule_delay = timedelta(hours=batch)
        schedule_time = datetime.now() + schedule_delay

        dispatch_deadline_seconds = 60 * 5  # 5 minutes

        backup_date = BACKUP_START_DATE + timedelta(days=i)
        body = {"data": {"date": backup_date.isoformat()[:10]}}
        task_options = functions.TaskOptions(
            schedule_time=schedule_time,
            dispatch_deadline_seconds=dispatch_deadline_seconds,
            uri=target_uri,
        )
        task_queue.enqueue(body, task_options)
    return https_fn.Response(status=200, response=f"Enqueued {BACKUP_COUNT} tasks")
  • 程式碼範例會為第 N 項工作設定 N 分鐘的延遲時間,嘗試分散工作執行時間。這相當於每分鐘觸發約 1 項工作。請注意,如果您希望 Cloud Tasks 在特定時間觸發工作,也可以使用 scheduleTime (Node.js) 或 schedule_time (Python)。

  • 程式碼範例會設定 Cloud Tasks 等待工作完成的時間上限。Cloud Tasks 會根據佇列的重試設定重試工作,或直到達到這個期限為止。在範例中,佇列設定為最多重試工作 5 次,但如果整個程序 (包括重試) 超過 5 分鐘,工作就會自動取消。

疑難排解

瞭解如何使用記錄功能診斷工作佇列問題,以及設定必要的 IAM 權限。

啟用 Cloud Tasks 記錄功能

Cloud Tasks 的記錄包含實用的診斷資訊,例如與工作相關聯的要求狀態。根據預設,Cloud Tasks 的記錄功能會關閉,因為這項服務可能會在專案中產生大量記錄。建議您在開發及偵錯工作佇列函式時,開啟偵錯記錄檔。請參閱「開啟記錄功能」。

IAM 權限

在將工作排入佇列,或 Cloud Tasks 嘗試叫用工作佇列函式時,您可能會看到 PERMISSION DENIED 錯誤。確認專案具備下列 IAM 繫結:

  • 用於將工作排入 Cloud Tasks 佇列的身分必須具備 cloudtasks.tasks.create IAM 權限。

    在範例中,這是 App Engine 預設服務帳戶

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member=serviceAccount:${PROJECT_ID}@appspot.gserviceaccount.com \
  --role=roles/cloudtasks.enqueuer
  • 用於將工作排入 Cloud Tasks 佇列的身分,必須具備使用 Cloud Tasks 中工作相關聯服務帳戶的權限。

    在範例中,這是 App Engine 預設服務帳戶。

如要瞭解如何將 App Engine 預設服務帳戶新增為 App Engine 預設服務帳戶的使用者,請參閱 Google Cloud IAM 說明文件。

  • 用於觸發工作佇列函式的身分必須具備 cloudfunctions.functions.invoke 權限。

    在範例中,這是 App Engine 預設服務帳戶

gcloud functions add-iam-policy-binding $FUNCTION_NAME \
  --region=us-central1 \
  --member=serviceAccount:${PROJECT_ID}@appspot.gserviceaccount.com \
  --role=roles/cloudfunctions.invoker