书接上文
上次谈到了如何搭建自己的faas?,被同行评论有些标题党,这篇文章将接着上文,来介绍一些干货。同时在写这篇文章的时候,核心功能vmbox已经开源,欢迎大家点赞fork。
faas
是云厂商提出的一种函数即服务
的程序部署模式,以函数为核心,实现以函数粒度的服务伸缩,这项技术非常复杂,有很多的技术资料,大家可以查看云厂商的文档。我们这里聊到的是比较简单的实现方式,以传统服务为基础,借用nodejs的沙盒能力,实现安全执行用户的函数,达到函数即服务的目的。
起因
相信大家项目中都会遇到多个项目使用同一个函数
,该函数具有规则复杂
、逻辑独立
等特点的纯函数,前端往往是发布一个npm包,直接引入,粗暴点的直接复制到另一个项目中。假设校验身份证件号的真伪等函数,有时后端也需要用,由于语言的差异,直接复用不太可能,将这类函数抽象成为单独的函数的想法便应运而生,决定尝试最前沿的编程思想(前端的世界就是疯狂试探),自建faas工程就这样开始了。
目标
将js函数转化为API服务,即实现函数即服务
安全执行环境vmbox
实现上述目标的核心难题是js函数的安全执行环境,上一期已经谈到node的沙盒存在的问题,这里不再赘述。具体的解决方案,已经开源,大家可以点击查看vmbox。对源码感兴趣的同学可以对照上篇文章中的流程图查看代码,欢迎贡献。
回归正题,vmbox
是经过真实项目检验的node沙盒库,具有六大特点
- 死循环强制退出(一旦执行超过指定时间,可能存在死循环,kill子进程,释放资源)
- 跨进程函数调用(使用IPC跨进程调用函数)
- 函数互相调用(借助context实现函数间调用)
- 内部任务队列(子进程忙碌状态,任务脚本进入队列等待)
- 进程自治(杀死自启动)
- 返回promise(编程体验上只需要作为一个异步任务即可)
vmbox的实例只有一个run
方法,返回值是一个promise
,接收三个参数
参数名 | 类型 | 是否选填 | 默认值 | 简介 |
---|---|---|---|---|
code | string | 必填 | - | 运行的js代码 |
context | object | 选填 | {} | 函数运行上下文 |
stack | boolean | 选填 | false | 函数内调用其他函数,记录函数调用栈 |
如果代码运行出错,会使用Promise.reject(error)抛出异常,需要对异常进行捕获
基本用法
const VMBox = require('vmbox');
const vmBox = new VMBox({
timeout: 100,
asyncTimeout: 500
});
const context = {
sum(a, b){
return a + b;
}
}
const fn = `sum(2, 3)`
vmBox.run(fn).then(console.log)
// 打印5
高级用法
借助函数运行上下文,可以做很多事情,下面实现了一个从函数内部调用其他函数的方法。
const VMBox = require('vmbox');
const vmBox = new VMBox({
timeout: 100,
asyncTimeout: 500
});
const fnGroup = {
sum: `async function main({params, fn}){
const {a, b} = params;
return a + b
}`,
caller: `async function main({params, fn}){
return await fn.call('sum', params);
}`
};
async function run(code, context, stack = false) {
const runCode = code + `;\n(async () => { return await main({params, fn}); })()`
return vmBox.run(runCode, context, stack);
}
const fn = {
call: (name, params) => {
const code = fnGroup[name];
if (code) {
return run(code, { params, fn }, true);
} else {
return null;
}
}
}
const context = {
fn,
params: {
a: 10,
b: 20
}
}
const code = fnGroup.caller;
try {
const res = await run(code, context);
console.log(res); // 打印30
} catch (error) {
console.log(error);
}
关注点
- vmbox能够解决
vm2
中异步死循环的问题,但是进程启动的成本比较高,因此在使用过程中尽量避免进程的重启,提高运行效率。 - context是函数的上下文,可以注入任何想提供给函数的功能,扩展函数的能力,比如:数据库访问能力、http能力等。
- 关注代码的同学会注意到,为什么不采用多进程模型而采用单进程?这里有两个理由,一是node服务运行环境一般单核,单进程实用性更强,如果需要多进程,可以采用node的cluster模块封装实现。二是需要实现函数间相互调用,如高级用法,多进程调用中存在死循环。