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 的價格打了下來 ~