將第一代 Node.js 功能升級到第二代

目前使用第一代功能的應用程式應考慮使用本指南中的說明遷移到第二代。第二代功能使用 Cloud Run 來提供更好的效能、更好的配置、更好的監控等等。

本頁中的範例假設您將 JavaScript 與 CommonJS 模組一起使用( require樣式匯入),但相同的原則也適用於 ESM( import … from樣式匯入)的 JavaScript 和 TypeScript。

遷移過程

第一代和第二代函數可以在同一文件中並存。當您準備好時,這可以讓您輕鬆地逐段遷移。我們建議一次遷移一項功能,在繼續之前執行測試和驗證。

驗證 Firebase CLI 和firebase-function版本

確保您至少使用 Firebase CLI 版本12.00firebase-functions版本4.3.0 。任何較新的版本都將支援第二代和第一代。

更新導入

第二代函數從firebase-functions SDK 中的v2子包導入。 Firebase CLI 需要使用此不同的導入路徑來確定是將函數程式碼部署為第一代函數還是第二代函數。

v2子包是模組化的,我們建議僅導入您需要的特定模組。

之前:第一代

const functions = require("firebase-functions");

之後:第二代

// explicitly import each trigger
const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

更新觸發器定義

由於第二代 SDK 支援模組化導入,因此請更新觸發器定義以反映上一步中變更的導入。

傳遞給某些觸發器的回呼的參數已變更。在此範例中,請注意onDocumentCreated回呼的參數已合併到單一event物件中。此外,有些觸發器具有方便的新配置功能,例如onRequest觸發器的cors選項。

之前:第一代

const functions = require("firebase-functions");

exports.date = functions.https.onRequest((req, res) => {
  // ...
});

exports.uppercase = functions.firestore
  .document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

之後:第二代

const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

exports.date = onRequest({cors: true}, (req, res) => {
  // ...
});

exports.uppercase = onDocumentCreated("my-collection/{docId}", (event) => {
  /* ... */
});

使用參數化配置

第二代函數放棄了對functions.config的支持,轉而採用更安全的接口,用於在程式碼庫中以聲明方式定義配置參數。使用新的params模組,除非所有參數都具有有效值,否則 CLI 會阻止部署,從而確保不會在缺少配置的情況下部署函數。

遷移到params子包

如果您一直在使用functions.config進行環境配置,則可以將現有配置遷移到參數化配置

之前:第一代

const functions = require("firebase-functions");

exports.date = functions.https.onRequest((req, res) => {
  const date = new Date();
  const formattedDate =
date.toLocaleDateString(functions.config().dateformat);

  // ...
});

之後:第二代

const {onRequest} = require("firebase-functions/v2/https");
const {defineString} = require("firebase-functions/params");

const dateFormat = defineString("DATE_FORMAT");

exports.date = onRequest((req, res) => {
  const date = new Date();
  const formattedDate = date.toLocaleDateString(dateFormat.value());

  // ...
});

設定參數值

首次部署時,Firebase CLI 會提示輸入所有參數值,並將這些值儲存在 dotenv 檔案中。若要匯出functions.config值,請執行firebase functions:config:export

為了額外的安全性,您還可以指定參數類型驗證規則

特殊情況:API 金鑰

params模組與 Cloud Secret Manager 集成,後者為 API 金鑰等敏感值提供細粒度的存取控制。有關詳細信息,請參閱秘密參數

之前:第一代

const functions = require("firebase-functions");

exports.getQuote = functions.https.onRequest(async (req, res) => {
  const quote = await fetchMotivationalQuote(functions.config().apiKey);
  // ...
});

之後:第二代

const {onRequest} = require("firebase-functions/v2/https");
const {defineSecret} = require("firebase-functions/params");

// Define the secret parameter
const apiKey = defineSecret("API_KEY");

exports.getQuote = onRequest(
  // make the secret available to this function
  { secrets: [apiKey] },
  async (req, res) => {
    // retrieve the value of the secret
    const quote = await fetchMotivationalQuote(apiKey.value());
    // ...
  }
);

設定運行時選項

第一代和第二代之間的運行時選項配置發生了變化。第二代也新增了為所有功能設定選項的新功能。

之前:第一代

const functions = require("firebase-functions");

exports.date = functions
  .runWith({
    // Keep 5 instances warm for this latency-critical function
    minInstances: 5,
  })
  // locate function closest to users
  .region("asia-northeast1")
  .https.onRequest((req, res) => {
    // ...
  });

exports.uppercase = functions
  // locate function closest to users and database
  .region("asia-northeast1")
  .firestore.document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

之後:第二代

const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");
const {setGlobalOptions} = require("firebase-functions/v2");

// locate all functions closest to users
setGlobalOptions({ region: "asia-northeast1" });

exports.date = onRequest({
    // Keep 5 instances warm for this latency-critical function
    minInstances: 5,
  }, (req, res) => {
  // ...
});

exports.uppercase = onDocumentCreated("my-collection/{docId}", (event) => {
  /* ... */
});

使用並行

第二代函數的一個顯著優點是單一函數實例能夠同時滿足多個請求。這可以顯著減少最終用戶經歷的冷啟動次數。預設情況下,並發設定為 80,但您可以將其設定為 1 到 1000 之間的任何值:

const {onRequest} = require("firebase-functions/v2/https");

exports.date = onRequest({
    // set concurrency value
    concurrency: 500
  },
  (req, res) => {
    // ...
});

調整並發性可以提高效能並降低函數成本。在允許並發請求中了解有關並發的更多資訊。

審計全域變數的使用情況

未考慮並發性而編寫的第一代函數可能會使用在每個請求上設定和讀取的全域變數。當啟用並發並且單一實例開始同時處理多個請求時,這可能會在您的函數中引入錯誤,因為並發請求開始同時設定和讀取全域變數。

升級時,您可以將函數的 CPU 設定為gcf_gen1並將concurrency設為 1 以恢復第一代行為:

const {onRequest} = require("firebase-functions/v2/https");

exports.date = onRequest({
    // TEMPORARY FIX: remove concurrency
    cpu: "gcf_gen1",
    concurrency: 1
  },
  (req, res) => {
    // ...
});

但是,不建議將此作為長期修復,因為它會喪失第二代功能的效能優勢。相反,請審核函數中全域變數的使用情況,並在準備好時刪除這些臨時設定。

將流量遷移到新的第二代功能

就像更改函數的區域或觸發類型一樣,您需要為第二代函數指定一個新名稱,並慢慢將流量遷移到它。

無法將同名的函數從第一代升級到第二代並執行firebase deploy 。這樣做會導致錯誤:

Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.

在執行這些步驟之前,首先確保您的函數是冪等的,因為在更改期間函數的新版本和舊版本將同時運行。例如,如果您有一個回應Firestore 中的寫入事件的第一代函數,請確保回應寫入兩次,一次由第一代函數回應,一次由第二代函數回應,以回應這些事件,從而使您的應用程式處於一致的狀態。

  1. 在函數程式碼中重新命名該函數。例如,將resizeImage重新命名為resizeImageSecondGen
  2. 部署函數,使原始第一代函數和第二代函數都運作。
    1. 對於可呼叫觸發器、任務佇列觸發器和 HTTP 觸發器,透過使用第二代函數的名稱或 URL 更新用戶端程式碼,開始將所有用戶端指向第二代函數。
    2. 透過後台觸發器,第一代和第二代功能將在部署後立即回應每個事件。
  3. 遷移所有流量後,使用 firebase CLI 的firebase functions:delete指令刪除第一代函數。
    1. (可選)重新命名第二代函數以符合第一代函數的名稱。