将第 1 代 Node.js 函数升级为第 2 代函数

目前使用第 1 代函数的应用应考虑按照本指南中的说明迁移到第 2 代函数。第 2 代函数使用 Cloud Run,能够提供性能、配置和监控等诸多方面的改进。

本页面中的示例假定您使用具有 CommonJS 模块(require 式导入)的 JavaScript,不过同样的操作原理也适用于具有 ESM(import … from 式导入)和 TypeScript 模块的 JavaScript。

迁移过程

第 1 代和第 2 代函数可以在同一个文件中共存;这让您可以在方便时,逐步完成迁移。我们建议您一次迁移一个函数,对其进行测试和验证,之后再进行后续迁移。

验证 Firebase CLI 和 firebase-function 版本

确保您至少使用的是 Firebase CLI 12.00 版和 firebase-functions 4.3.0 版。后续所有版本都同时支持第 2 代和第 1 代函数。

更新导入语句

第 2 代函数是从 firebase-functions SDK 中的 v2 子软件包导入的。Firebase CLI 只需知晓这一导入路径,即可确定是将您的函数代码部署为第 1 代函数还是第 2 代函数。

v2 子软件包采用模块化结构,建议您仅导入自己需要的特定模块。

之前:第 1 代

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

之后:第 2 代

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

更新触发器定义

由于第 2 代 SDK 偏好模块化导入,因此请更新触发器定义以反映在上一步中更改的导入项。

对于某些触发器,传递给回调函数的参数发生了变化。请注意,在此示例中,onDocumentCreated 回调函数的参数已被整合到单个 event 对象中。此外,我们还为某些触发器新增了一些便捷的配置功能,例如 onRequest 触发器的 cors 选项。

之前:第 1 代

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

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

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

之后:第 2 代

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) => {
  /* ... */
});

使用参数化配置

第 2 代函数不再支持 functions.config,代之以一个更安全的接口,让您能够在代码库内以声明方式定义配置参数。在新的 params 模块中,除非所有参数都具有有效值,否则 CLI 会阻止部署,这可确保系统不会部署配置有缺失的函数。

迁移到 params 子软件包

如果您一直将环境配置与 functions.config 搭配使用,则可以将现有配置迁移到参数化配置

之前:第 1 代

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

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

  // ...
});

之后:第 2 代

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 密钥等敏感值的精细访问权限控制。如需了解详情,请参阅 Secret 参数

之前:第 1 代

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

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

之后:第 2 代

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());
    // ...
  }
);

设置运行时选项

第 1 代和第 2 代函数的运行时选项的配置也有所不同。第 2 代函数还新增了一项功能,可用于为所有函数设置选项。

之前:第 1 代

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

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) => {
    // ...
  });

之后:第 2 代

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) => {
  /* ... */
});

使用并发设置

第 2 代函数的一大优势是使用单个函数实例就能够同时处理多个请求;这会大幅减少最终用户遭遇冷启动的次数。并发设置的默认值为 80,但您可以将其设为 1 到 1,000 之间的任何值:

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

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

通过合理调整并发设置,可以提高性能并降低函数费用。如需详细了解并发设置,请参阅允许并发请求

审核全局变量使用情况

第 1 代函数在编写时并未考虑并发设置,它们可能会使用为每个请求设置和读取的全局变量。启用并发设置并开始使用单个实例同时处理多个请求时,便可能会导致您的函数出错,因为这些并发请求会同时设置和读取全局变量。

升级时,您可以将函数的 CPU 设为 gcf_gen1,并将 concurrency 设为 1,这样便可恢复第 1 代函数的行为:

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

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

但是,我们不建议将此作为长期的修复方案,因为这放弃了第 2 代函数的性能优势。建议您审核函数中全局变量的使用情况,并在准备就绪后移除这些临时设置。

将流量迁移到新的第 2 代函数

就像更改函数的区域或触发器类型一样,您需要为第 2 代函数指定新名称,并缓慢地向其迁移流量。

无法使用相同名称将函数从第 1 代升级到第 2 代并运行 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 代函数,请确保在响应这些事件时,对写入做出两次响应(一次由第 1 代函数响应,一次由第 2 代函数响应)会使您的应用处于一致的状态。

  1. 在函数代码中重命名函数。例如,将 resizeImage 重命名为 resizeImageSecondGen
  2. 部署函数,以便运行原始的第 1 代函数和第 2 代函数。
    1. 对于 Callable、任务队列和 HTTP 触发器,请使用第 2 代函数的名称或网址更新客户端代码,以开始将所有客户端指向第 2 代函数。
    2. 使用后台触发器时,第 1 代和第 2 代函数会在部署后立即响应每个事件。
  3. 完成所有流量迁移后,使用 Firebase CLI 的 firebase functions:delete 命令删除第 1 代函数。
    1. (可选)重命名第 2 代函数以匹配第 1 代函数的名称。