首页 > 解决方案 > 在多个云功能中最小化冷启动时间的最佳方法

问题描述

正如您可能在下面看到的那样;这里有两种不同的方法来运行不同的云功能,如 foo.js 和 bar.js。

在方法 #1 中,admin、database 和消息模块在 index.js 中初始化并作为参数传递给每个相关函数。此外,在方法 #2 中,这些参数是在每个函数内部定义的。

哪种方法适用于最小化运行每个功能期间的冷启动时间?

方法#1

index.js

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

const admin = require('firebase-admin');
admin.initializeApp();
const database = admin.database();
const messaging = admin.messaging();


const fooFunction = require('./foo');
exports.fooFunction = functions.database.ref('/users/messages_inbox').onCreate( (snapshot, context) => { fooFunction.handler(database, messaging, snapshot, context) });

const barFunction = require('./bar');
exports.barFunction = functions.database.ref('/users').onCreate( (snapshot, context) => { barFunction.handler(database, snapshot, context) });

foo.js

exports.handler = (database, messaging, snapshot, context) => {

  // some function
}

bar.js

exports.handler = (database, snapshot, context) => {

  // some function
}

方法#2

index.js

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

const fooFunction = require('./foo');
exports.fooFunction = functions.database.ref('/users/messages_inbox').onCreate( fooFunction.handler );

const barFunction = require('./bar');
exports.barFunction = functions.database.ref('/users').onCreate( barFunction.handler );

foo.js

exports.handler = (snapshot, context) => {
  const admin = require('firebase-admin');
  const database = admin.database();
  const messaging = admin.messaging();

  // some function
}

bar.js

exports.handler = (snapshot, context) => {
  const admin = require('firebase-admin');
  const database = admin.database();

  // some function
}

标签: node.jsfirebasegoogle-cloud-functions

解决方案


TL;DR:从冷启动的角度来看,“方法 2”看起来更有利,因为您 (A) 避免加载不需要的模块,并且 (B) 避免实例化不必要的服务,直到它们在您的代码中真正需要它们

长答案:

为什么“方法#2”导致更快的冷启动主要有两个方面起作用:

第一个方面是,通过require在处理程序中使用本地语法而不是使用 global imports,您可以有效地避免加载任何不需要的模块。在具有许多处理程序的项目中,这些处理程序依赖于完全不同的模块和服务集,这可以显着提高速度。

最重要的是,它还将优化您的云功能服务器实例的资源使用。这是因为在部署您的项目和使用云功能时,firebase 将针对每个云功能服务器实例运行您的完整项目代码。然后它只使用这个专用服务器实例来运行您项目的一个专用云功能。因此,如果您使用import语法(如“方法 #1”),这将导致项目中所有函数的所有导入依赖项不必要地包含在内,即使它们从不需要。– 您仅通过本地延迟加载模块的方法require有效地防止了这种情况,因此理论上加快了冷启动。

“方法#2”的第二个方面是,除了避免加载不必要的模块(通过 的帮助require())之外,您还在延迟“服务”(例如admin.database())的实例化,直到您真正需要它们。

从绝对性能的角度来看,最好不要实例化任何服务,直到需要它们,如“方法#2”中所做的那样。因此,如果您的 firebase 事件处理程序不一定依赖“数据库”或“消息”服务,您可以通过不在全局项目范围内实例化它们来加快处理速度。考虑到 Firebase 服务的初始化性能在未来可能会发生变化时尤其如此——它们现在很快,但谁知道呢,也许调用admin.database()在不久的将来会变得更繁重。这就是为什么最好的做法是假设最坏的情况并将 3rd 方库视为不受您控制的黑盒,并且您不能过多地依赖性能明智的做法。

缺点:

在你的函数中实例化服务(如“方法#2”中所做的那样)违背了依赖注入的想法并使单元测试更加困难,你应该考虑这一点。

此外,通过有条件地在函数体内加载 JS 模块require()是以可读性和代码可维护性为代价的。此外,它使自动化静态代码可分析性成为一项具有挑战性的任务,并阻止像 webpack 这样的工具进行适当的tree shacking 。这可能看起来很讽刺,但是虽然您可能成功地优化了 JS 代码的初始化时间,但在较低级别上,增加的包大小可能会次优地影响 gcloud 容器衍生,从而影响冷启动时间(请参阅包大小是否重要?),因为整个项目图像更大,容器实例本身需要更长的时间来创建。

最后,您应该注意不要对项目进行微优化,除非您遇到性能瓶颈,而是保持良好的可读性项目结构。


推荐阅读