node.js - 在多个云功能中最小化冷启动时间的最佳方法
问题描述
正如您可能在下面看到的那样;这里有两种不同的方法来运行不同的云功能,如 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
}
解决方案
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 容器衍生,从而影响冷启动时间(请参阅包大小是否重要?),因为整个项目图像更大,容器实例本身需要更长的时间来创建。
最后,您应该注意不要对项目进行微优化,除非您遇到性能瓶颈,而是保持良好的可读性项目结构。
推荐阅读
- php - 如何从我的文件中显示每个选项的特定信息?
- javascript - 提交输入字段时更改按钮颜色
- c# - 从现有的层次结构创建不可变的集合结构
- php - 是否可以在 PHP 的 for 循环中使用三元运算符?
- mysql - 需要替换 mysql 列中的特定字符串
- python-3.x - 在熊猫中分组并按条件删除
- python - 如何编写 Python 递归算法来查找两个值(来自用户)之间的回文素数?
- java - 如何修复:“java.lang.ClassCastException:com.mashape.unirest.http.HttpResponse 无法转换为 org.apache.http.HttpResponse”
- sql - 基于子查询使用游标更新数据库的替代方法
- django - 如何在 Django 中获取组模型的所有选定权限