函数模式
可测试性
const db = require("db").connect();
const mailer = require("mailer");
module.exports.saveUser = (event, context, callback) => {
const user = {
email: event.email,
created_at: Date.now()
};
db.saveUser(user, function(err) {
if (err) {
callback(err);
} else {
mailer.sendWelcomeEmail(event.email);
callback();
}
});
};
这个例子主要存在两个问题:
- 业务逻辑和
FaaS 耦合在一起。主要就是业务逻辑都在saveUser 这个函数里,而saveUser 参数的event 和conent 对象,是FaaS 平台提供的。 - 业务逻辑和
BaaS 耦合在一起。具体来说,就是函数内使用了db 和mailer 这两个后端服务,测试函数必须依赖于db 和mailer 。
基于将业务逻辑和函数依赖的
class Users {
constructor(db, mailer) {
this.db = db;
this.mailer = mailer;
}
save(email, callback) {
const user = {
email: email,
created_at: Date.now()
};
this.db.saveUser(user, function(err) {
if (err) {
callback(err);
} else {
this.mailer.sendWelcomeEmail(email);
callback();
}
});
}
}
module.exports = Users;
const db = require("db").connect();
const mailer = require("mailer");
const Users = require("users");
let users = new Users(db, mailer);
module.exports.saveUser = (event, context, callback) => {
users.save(event.email, callback);
};
在重构后的代码中,我们将业务逻辑全都放在了
// 模拟 mailer
const mailer = {
sendWelcomeEmail: email => {
console.log(`Send email to ${email} success!`);
}
};
冷启动
当驱动函数执行的事件到来的时候,首先需要下载代码,然后启动一个容器,在容器里面再启动一个运行环境,最后才是执行代码。前几步统称为冷启动(Cold Start
- 增加函数的内存可以减少冷启动时间
- C#、
Java 等编程语言的能启动时间大约是Node.js 、Python 的100 倍
以
执行上下文重用
const mysql = require("mysql");
module.exports.saveUser = (event, context, callback) => {
// 初始化数据库连接
const connection = mysql.createConnection({
/* ... */
});
connection.connect();
connection.query("...");
};
上面例子实现的功能就是在
既然在短时间内,函数的执行上下文可以重复利用,那么我们就可以将数据库连接放在函数之外:
const mysql = require("mysql");
// 初始化数据库连接
const connection = mysql.createConnection({
/* ... */
});
connection.connect();
module.exports.saveUser = (event, context, callback) => {
connection.query("...");
};
这样就只有第一次运行环境启动的时候,才会初始化数据库连接。后续请求来临、执行函数的时候,就可以直接利用执行上下文中的
函数预热
既然函数的运行环境会保留一段时间,那么我们也可以通过主动调用函数的方式,隔一段时间就冷启动一个运行环境,这样就能使得其他正常的请求都是热启动,从而避免冷启动时间对函数性能的影响。
这是目前比较有效的方式,但也需要有一些注意的地方:
- 不要过于频繁调用函数,至少频率要大于
5 分钟 - 直接调用函数,而不是通过网关等间接调用
- 创建专门处理这种预热调用的函数,而不是正常业务函数
这种方案只是目前行之有效且比较黑科技的方案,可以使用,但如果你的业务允许“牺牲第一个请求的性能换取大部分性能”,那也完全不必使用该方案,