降低代码的复杂性以使其更快;你们中的一些人正在使用微服务或纳米服务架构,例如 firebase 函数、AWS lambda 或 Kubernetes,尝试通过在函数内部而不是全局范围内初始化库来减少死启动和冷启动,
如果您有多个 API 调用,请尝试使其并行而不是一个接一个地减少。例如promise.all 方法
也可以通过数据库或上下文来解决问题。
例如用户问:我的余额是多少
Bot:我正在检查你的余额。过几秒再问
并在后台获取耗时API并将数据保存在MongoDB等高速数据库中(相对于慢速Web服务API),并在上下文菜单或数据库中标记一个标志。
当用户几秒钟后再次询问时,检查标志是否为肯定从高速数据库中获取数据并提供给用户
提示:如果您使用的是谷歌助手,您可以在从 API 获取数据完成时发送推送通知
更新:
回复评论:“您能解释一下“在函数内部而不是全局范围内初始化库”是什么意思吗?”
例如,在 firebase 函数的情况下,它实际上是在容器化环境中执行的,当您暂时不调用该函数时,它只会从内存中释放函数的容器,当您再次调用它时,它会初始化在实际执行之前再次调用容器,该初始化称为冷启动,因此第一次调用需要更多时间,随后调用需要更少,即使第一次调用的执行时间相同但函数无法执行直到容器初始化完成,容器的初始化包括所有的库和数据库连接初始化等。这一切都很好,您无法摆脱微/纳米服务架构中的冷启动,但有时它会花费越来越多的时间并导致用户感到沮丧和糟糕的体验,并且像 dialogflow first call 这样的服务每次都会失败,这不好,这里还有更多:像firebase这样的服务实际上为每个函数创建了单独的容器,例如,如果你有多个函数,firebase实际上将每个函数部署在一个单独的容器中,所以调用每个函数只初始化那个函数的容器而不是所有其他函数容器,这里是真正的问题来了,您调用一个函数并在全局范围内初始化所有内容,无论您的函数是否使用它,大多数开发人员都会犯错误,他们在全局范围内初始化数据库,这意味着每个函数都必须在冷启动时对其进行初始化,但不是全部你的函数实际上使用数据库连接,所以我们需要在每个函数的主体中分别初始化数据库,然后不在函数之外,事实上我做的是我做一个可重用的函数来检查数据库是否尚未连接,否则什么都不做,这个检查是为了避免在每个函数调用中初始化数据库,这可能会导致执行时间增加。
稍后我会尝试添加 firebase 函数代码示例。
更新 2:
这里是代码示例
传统方式:
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as _cors from 'cors';
import firestore from './../db'
import * as mongoose from "mongoose";
const defaultApp = admin.initializeApp(functions.config().firebase)
const dbURI = `mongodb://xxxxxx:xxxxxx@ds00000.mlab.com:123456/mydb`;
// const dbURI = `mongodb://localhost:27017/mydb`;
mongoose.connect(dbURI, {
useNewUrlParser: true, useUnifiedTopology: true
}).catch(e => {
console.log("mongo connection failed for reason: ", e);
})
var cors = _cors({ origin: true });// set these options appropriately According to your case,
// see document: https://www.npmjs.com/package/cors#configuration-options
// true means allow everything
// http example
export const addMessage = functions.https.onRequest((req, res) => {
const original = req.query.text;
admin.database().ref('/messages').push({ original: original }).then(snapshot => {
res.redirect(303, snapshot.ref);
});
});
export const signup = functions.https.onRequest(async (req, res) => {
... signup stuff using mongodb
res.send("user signed up");
})
//databse trigger example
export const makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite(event => {
const original = event.data.val();
console.log('Uppercasing', event.params.pushId, original);
const uppercase = original.toUpperCase();
return event.data.ref.parent.child('uppercase').set(uppercase);
});
//cors example
export const ping = functions.https.onRequest(async (req, res) => {
cors(req, res, () => {
res.send("this is a function");
})
})
在上面的代码中,你可能会注意到 4 个函数
- HTTP 触发器 addMessage 用于在 Firebase DB 中添加消息
- HTTP 注册功能,使用 MongoDB
- 数据库触发器将条目设为大写,不使用任何数据库
- HTTP 触发 ping 功能,不使用任何数据库
您可能还会注意到两个数据库初始化,firebase 和 MongoDB
假设你在一段时间后第一次调用一个函数并且该函数是冷的,所以它会初始化这两个数据库,不仅是一次而且分别对所有四个函数进行初始化,假设每个数据库初始化需要 400 毫秒,所以这两个需要 800 英里,所以当你调用第一个函数来添加一条消息时,它会初始化两个 db(800ms) 然后它会实际执行函数(比如说 150ms) 所以 800ms+150ms 所以大约需要 950ms第一次,不管它没有使用 mongodb,它都会初始化它,因为初始化是写在全局范围内的
如果您在 addMessage 函数之后调用注册函数,它将为 db init 执行相同的 800 毫秒,然后注册函数执行假设它需要 200 毫秒,因此总共 800+200=1000 毫秒,您可能会认为 db 已经初始化所以为什么再说一次,正如我在最初的回答中已经提到的那样,每个函数可能存在于单独的容器中(并非总是如此,但确实如此)这意味着注册函数可能不知道 addMessage 函数中发生了什么,因此它将为其容器初始化数据库所以第一次通话会比后续通话花费更多时间
函数 3 是一个 db 触发器,它没有使用数据库,但是当它被调用时,它会接收到数据库的句柄并使用该句柄在数据库中进行更改,但在这种情况下,当函数是冷的并且你创建了一个在 db 中的条目它实际上像任何其他函数一样初始化函数,这意味着第一次仍然存在 800ms 开销,这就是大多数人讨厌 db 触发器但他们不知道为什么会发生的原因(此时我想提一下他们的设计中除了冷启动之外几乎没有什么东西,而且 github 上也有问题,但相信我优化冷启动会解决你的问题 50%)
函数 4 只不过是一个 ping 函数,但它也会初始化数据库,800 毫秒的开销
现在看看以下经过一些优化的代码:
您可能会注意到,我没有在全局范围内直接初始化数据库,而是在全局范围内注册了一个名为 initMongodb 的子例程函数,其中包含数据库初始化逻辑,因此当您调用 firebase 函数时,它不会在冷启动期间初始化数据库,而只会注册此子例程函数在全局范围内,因此您可以通过任何 firebase 函数访问它,
现在,如果您观察第二个函数,即注册,您可能已经注意到我使数据库初始化进一步有条件,因为如果函数没有接收到正确的数据来执行注册,那么初始化数据库有什么意义,此时我会想提一下,如果数据库初始化完成一次,那么在后续调用中它实际上不会再次初始化数据库,实际上当firebase函数执行完成时,它会破坏该firebase函数范围内的所有变量,但它会保留全局变量(直到下一个冷启动),您可能会注意到我需要 mongodb 作为变量名称 mongoose 和 firebase 作为变量名称 admin 在全局范围内,初始化会对这些变量进行一些更改,这就是为什么初始化逻辑是有条件的,如果 db未初始化则初始化,否则什么也不做。
这里要注意的另一点是“不要”尝试将所有内容保留在 firebase 函数本地范围内(例如导入 mongoose 以及初始化 mongoose 和其他 DB),这将使开销永久化,并将导入和每次调用都从头开始初始化数据库,因为所有局部变量在执行完成后都会被销毁,所以它比冷启动本身更危险
最后,如果您观察函数 3 和 4,将不会进行数据库初始化,但这并不意味着在冷启动和后续调用中会花费相同的时间,在导入等过程中仍然会发生一些事情它将库文件从磁盘加载到内存,但与 db 初始化(向互联网上的其他计算机发出 https/socket 请求)相比,这并不需要那么长的时间,导入都发生在同一台计算机上,仍然更好以避免生产中不必要的导入。
冷启动优化方式(推荐)
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as _cors from 'cors';
import firestore from './../db'
import * as mongoose from "mongoose";
const dbURI = `mongodb://xxxxxx:xxxxxx@ds00000.mlab.com:123456/mydb`;
// const dbURI = `mongodb://localhost:27017/mydb`;
export functions initFirebase(){
if (admin.apps.length === 0) {
console.log("initializing firebase database");
admin.initializeApp(functions.config().firebase)
}else{
console.log("firebase is already initialized");
}
}
export function initMongoDb() {
if (mongoose.connection.readyState !== mongoose.STATES.connected
&& mongoose.connection.readyState !== mongoose.STATES.connecting) {
console.log("initializing mongoose");
mongoose.connect(dbURI, {
useNewUrlParser: true, useUnifiedTopology: true
}).catch(e => {
console.log("mongo connection failed for reason: ", e);
})
} else {
console.log("mongoose already connected: ", mongoose.STATES[mongoose.connection.readyState]);
}
}
var cors = _cors({ origin: true });// set these options appropriately According to your case,
// see document: https://www.npmjs.com/package/cors#configuration-options
// true means allow everything
// http example
export const addMessage = functions.https.onRequest((req, res) => {
initFirebase()
const original = req.query.text;
admin.database().ref('/messages').push({ original: original }).then(snapshot => {
res.redirect(303, snapshot.ref);
});
});
export const signup = functions.https.onRequest(async (req, res) => {
if(req.body.name && req.body.email && req.body.password){
initMongoDb();
... signup stuff using mongodb
res.send("user signed up");
}else{
res.status(400).send("parameter missing");
}
})
//database trigger example
export const makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite(event => {
const original = event.data.val();
console.log('Uppercasing', event.params.pushId, original);
const uppercase = original.toUpperCase();
return event.data.ref.parent.child('uppercase').set(uppercase);
});
//cors example
export const function3 = functions.https.onRequest(async (req, res) => {
cors(req, res, () => {
res.send("this is a function");
})
})
Update: a ping call to function on start of mobile app or on page load in web also works well
Inzamam Malik,
Web & Chatbot developer.
malikasinger@gmail.com