开源 Serverless 框架 Laf 性能优化实践
介绍
Laf 是一个完全开源的 Serverless 框架,Laf 的 Node.js 运行时容器 (以下简称为 Runtime) 是 Laf 的函数执行环境,依托于 Express.js 框架。采用容器进程常驻的方式,每一个应用对应于一个或多个容器 (弹性伸缩下),底层使用了 Node.js 的 vm 模块,使用 MongoDB 的 watch()
方法来监听函数变更事件,以实现函数发布和配置发布。
Node.js vm 模块
Node.js 的 vm 模块是一个提供虚拟机功能的模块,用于在 Node.js 环境中创建一个独立的 JavaScript 执行环境。它允许在应用程序中运行和控制一段 JavaScript 代码,同时提供了一些安全性和隔离性。
这个模块包括一些可用于创建隔离的执行环境的函数,使得代码能够在独立的上下文中运行,防止对主应用程序的影响。这在某些情况下可以提供更高的安全性,例如在沙盒环境中执行用户提供的代码,或者实现一些动态加载和执行代码的需求。
为什么要优化
目前 Laf 的函数运行时存在以下问题:
- 频繁使用 Node.js vm 模块重复创建 vm,vm 创建执行的过程中,CPU 消耗很高。在以下对 runtime 的 CPU 火焰图分析可见,在函数执行过程中,有两部分 CPU 执行时间较长,分别是输出函数请求日志和 vm 创建执行过程。
- 有时候遇到复杂的函数嵌套引用的时候,会导致循环引用,内存迟迟无法回收,造成内存泄漏,导致 OOM Killed。
- 交由 runtime 自己通过 HTTP 调用的形式,异步请求持久化函数日志,性能损耗大,QPS 直接减半。
- 函数引擎这块的逻辑越来越复杂和臃肿,维护难度很大,急需重构。
如何优化
在前面的分析中,我们知道,当前造成性能瓶颈的原因主要有两点:
- 为了实现隔离,vm 模块重复创建,CPU 消耗高,特别是当函数引用达到一定规模时。另一方面,复杂的引用下,甚至会发生内存难以回收造成内存泄漏的问题。
- 频繁打印函数请求日志,依赖单线程的 Node.js 通过异步请求处理 console.log 等日志,导致实际业务请求吞吐量下降。
因此,我们采用以下优化思路:
-
日志方面:使用标准输出的形式输出日志,交由 K8s 自己采集日志,而不由 runtime 自己处理。
-
函数引擎方面:第一次函数调用时,构建并缓存函数模块,下次调用直接取出使用,不需要重复编译,这块更改需要确保以下因素:
- 保证这个缓存的函数模块是无状态,即 y = f(x),输入相同的 x,则必然输出确定的 y。
- 函数发布时,要及时清理缓存的函数模块。
优化前后架构对比分析
- 优化前:
- 优化后:
优化步骤
- 改造日志方案为容器日志标准输出,交由 K8s 收集,完全去除日志的有状态依赖。
- 重构函数引擎,建立函数模块,每一个函数模块的导出都是一个 JS 对象,无论是代码还是引用的第三方包,都被视作为一个 Module,在代码中只会存在一份,等同于原生的 require / export:
- 简化代码,尽可能复用,保留核心逻辑;
- 去除函数模块中的有状态部分;
- 在函数执行、函数引入处建立函数模块缓存。
- 针对调试模式,每次函数执行时重新构建函数模块,主动收集执行日志。
核心函数调用逻辑
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 核。
附加彩蛋
除此之外,我们还做了不少额外的工作:
- 日志支持根据不同 Level,以不同的颜色输出。
- 通过重定向自定义依赖安装路径,现在支持安装和内置依赖版本不同的依赖包。
- 拦截器现在支持类似 koa 洋葱圈结构的前拦截和后拦截的写法,详情查看 Laf 文档。
- ...
总结
通过优化 Laf 运行时,我们在将每个应用的成本降低至原来的 1/10 的同时,还大大提高了性能和稳定性,成功把 Laf 的价格打了下来 ~

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
特殊字符:安全攻防中容易遗漏的细节
本文分享自华为云社区《【安全攻防】深入浅出实战系列专题-特殊字符校验》,作者: MDKing。 特殊字符校验的背景 SQL注入、XSS等常见的安全攻击场景会涉及到一些特殊字符的利用。尤其是界面输入框、API接口可支持输入字符串的情况,如果对来历不明的用户输入如果不加防范,很容易产生安全问题。 正确的做法是在根据业务流程在各个环节添加合适的安全防护、处理逻辑。例如:对于输入的字符串参数做一定的参数校验、对参与执行SQL操作的参数进行预编译、对于参与界面回显的参数针对回显的上下文领域做对应领域的编码等。 由于根据不同的业务流程需要针对特殊字符做的安全防护各有不同,相应的设计、实现等成本较高,所以通常大家会倾向于尽量在第一道防线(参数校验)上解决大部分的问题,降低后续流程处理的工作量跟难度。所以本文旨在分析探讨如何在参数校验阶段,设计好针对不可信字符串的校验、过滤,尤其是特殊字符相关的校验。 特殊字符校验的原则 参考华为Web应用安全开发规范:确保输入数据只包含允许的字符集,不包含不合法和危险的字符,尽可能采取“白名单”的方式进行输入校验。 总体思路是分两种情况: 输入范围较为明确的字段,...
- 下一篇
百度搜索展现服务重构:进步与优化
作者 |瞭东 导读 本文将简单介绍搜索展现服务发展过程,以及当前其面临的三大挑战:研发难度高、架构能力欠缺、可复用性低,最后提出核心解决思路和具体落地方案,期望大家能有所收货和借鉴。 全文4736字,预计阅读时间12分钟。 01 背景 百度搜索展现服务的主要职责是请求检索系统获取结果,并依次进行模板选择、实时摘要补充、数据适配和结果渲染,将检索结果能以丰富多样的形式精准地展示给用户。在初期,这项服务基于C语言进行开发,迭代效率不尽人意。随着产品的迅速迭代和业务的不断拓展,研发效率问题逐渐凸显,为了解决这一瓶颈,搜索展现服务进化为由PHP开发、HHVM运行的服务。目前,搜索展现服务由数十个产品线、上百个研发RD共同参与研发,承载了数百个精细化的业务展现策略。然而,随着搜索业务的日益复杂化和生成式大模型的崛起,搜索展现服务也开始面临研发难度增大、架构能力不足和复用性低等多重挑战。具体表现如下: 【研发难度高】:搜索展现服务基于过程管理,逻辑复杂,多个策略框架分布于代码的各个阶段,不能满足多业务对于简化管理、易于扩展的效率诉求 【架构能力欠缺】:hhvm基础设施已经停止维护,对于异步/多线程...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS关闭SELinux安全模块