Laf 是一个完全开源的 Serverless 框架,Laf 的 Node.js 运行时容器 (以下简称为 Runtime) 是 Laf 的函数执行环境,依托于 Express.js 框架。采用容器进程常驻的方式,每一个应用对应于一个或多个容器 (弹性伸缩下),底层使用了 Node.js 的 vm 模块,使用 MongoDB 的 watch()
方法来监听函数变更事件,以实现函数发布和配置发布。
Node.js 的 vm 模块是一个提供虚拟机功能的模块,用于在 Node.js 环境中创建一个独立的 JavaScript 执行环境。它允许在应用程序中运行和控制一段 JavaScript 代码,同时提供了一些安全性和隔离性。
这个模块包括一些可用于创建隔离的执行环境的函数,使得代码能够在独立的上下文中运行,防止对主应用程序的影响。这在某些情况下可以提供更高的安全性,例如在沙盒环境中执行用户提供的代码,或者实现一些动态加载和执行代码的需求。
目前 Laf 的函数运行时存在以下问题:
在前面的分析中,我们知道,当前造成性能瓶颈的原因主要有两点:
因此,我们采用以下优化思路:
日志方面:使用标准输出的形式输出日志,交由 K8s 自己采集日志,而不由 runtime 自己处理。
函数引擎方面:第一次函数调用时,构建并缓存函数模块,下次调用直接取出使用,不需要重复编译,这块更改需要确保以下因素:
const vm = require('vm')
// 函数列表
const functionList = {
a: "const b = require('b'); const func = () => b(); module.exports = func",
b: "module.exports = () => 'hello world'"
}
// 函数模块缓存
const functionModuleCache = new Map()
// 构建函数模块
const buildFunctionModule = (name) => {
// 自定义 require 逻辑,用来加载函数
const customRequire = (specifier) => {
if (functionModuleCache.has(specifier)) {
return functionModuleCache.get(specifier)
}
if(functionList[specifier]) {
return buildFunctionModule(specifier)
}
return require(specifier)
}
// 全局上下文
const ctx = {
__require: customRequire,
module: {
exports: {},
}
}
// 重新定义 require
const wrapCode = code => {
return `
const require = (name) => {
return __require(name)
}
${code}
module.exports;
`
}
// 构建模块
const script = new vm.Script(wrapCode(functionList[name]))
const mod = script.runInNewContext(ctx)
// 缓存构建结果
functionModuleCache.set(name, mod)
return mod
}
// 简单写一个入口函数
const main = () => {
const func = buildFunctionModule('a')
const res = func()
console.log(res)
}
main()
下面以 Laf 应用最低配置 0.1c 128m 为例进行压测。
常规 HTTP 请求:
数据量 | 测试结果 | QPS |
---|---|---|
10 并发请求 1000 次 | 110 | |
100 并发请求 1000 次 | 122 |
WebSocket 连接
每秒创建 100 个 websocket 连接,当创建 1 万个 websocket 连接时,资源占用情况如下:
某个跑在 laf 上的应用,日活数十万,原来需要 4 个 G 的内存,优化后,内存降至 512 MB 以下,CPU 只需要不到 1 核。
除此之外,我们还做了不少额外的工作:
通过优化 Laf 运行时,我们在将每个应用的成本降低至原来的 1/10 的同时,还大大提高了性能和稳定性,成功把 Laf 的价格打了下来 ~
|