介绍

Laf 是一个完全开源的 Serverless 框架,Laf 的 Node.js 运行时容器 (以下简称为 Runtime) 是 Laf 的函数执行环境,依托于 Express.js 框架。采用容器进程常驻的方式,每一个应用对应于一个或多个容器 (弹性伸缩下),底层使用了 Node.js 的 vm 模块,使用 MongoDB 的 watch() 方法来监听函数变更事件,以实现函数发布和配置发布。

Node.js vm 模块

Node.js 的 vm 模块是一个提供虚拟机功能的模块,用于在 Node.js 环境中创建一个独立的 JavaScript 执行环境。它允许在应用程序中运行和控制一段 JavaScript 代码,同时提供了一些安全性隔离性

这个模块包括一些可用于创建隔离的执行环境的函数,使得代码能够在独立的上下文中运行,防止对主应用程序的影响。这在某些情况下可以提供更高的安全性,例如在沙盒环境中执行用户提供的代码,或者实现一些动态加载执行代码的需求。

原文链接:https://forum.laf.run/d/1146

为什么要优化

目前 Laf 的函数运行时存在以下问题:

  1. 频繁使用 Node.js vm 模块重复创建 vm,vm 创建执行的过程中,CPU 消耗很高。在以下对 runtime 的 CPU 火焰图分析可见,在函数执行过程中,有两部分 CPU 执行时间较长,分别是输出函数请求日志vm 创建执行过程

  1. 有时候遇到复杂的函数嵌套引用的时候,会导致循环引用,内存迟迟无法回收,造成内存泄漏,导致 OOM Killed。
  2. 交由 runtime 自己通过 HTTP 调用的形式,异步请求持久化函数日志,性能损耗大,QPS 直接减半
  3. 函数引擎这块的逻辑越来越复杂和臃肿,维护难度很大,急需重构。

如何优化

在前面的分析中,我们知道,当前造成性能瓶颈的原因主要有两点:

  1. 为了实现隔离,vm 模块重复创建,CPU 消耗高,特别是当函数引用达到一定规模时。另一方面,复杂的引用下,甚至会发生内存难以回收造成内存泄漏的问题。
  2. 频繁打印函数请求日志,依赖单线程的 Node.js 通过异步请求处理 console.log 等日志,导致实际业务请求吞吐量下降。

因此,我们采用以下优化思路:

  1. 日志方面:使用标准输出的形式输出日志,交由 K8s 自己采集日志,而不由 runtime 自己处理。

  2. 函数引擎方面:第一次函数调用时,构建并缓存函数模块,下次调用直接取出使用,不需要重复编译,这块更改需要确保以下因素:

    1. 保证这个缓存的函数模块是无状态,即 y = f(x),输入相同的 x,则必然输出确定的 y。
    2. 函数发布时,要及时清理缓存的函数模块。

优化前后架构对比分析

  • 优化前:

  • 优化后:

优化步骤

  1. 改造日志方案为容器日志标准输出,交由 K8s 收集,完全去除日志的有状态依赖。
  2. 重构函数引擎,建立函数模块,每一个函数模块的导出都是一个 JS 对象,无论是代码还是引用的第三方包,都被视作为一个 Module,在代码中只会存在一份,等同于原生的 require / export:
    1. 简化代码,尽可能复用,保留核心逻辑;
    2. 去除函数模块中的有状态部分;
    3. 在函数执行、函数引入处建立函数模块缓存
  3. 针对调试模式,每次函数执行时重新构建函数模块,主动收集执行日志。

核心函数调用逻辑

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 为例进行压测。

  1. 常规 HTTP 请求:

    数据量 测试结果 QPS
    10 并发请求 1000 次 110
    100 并发请求 1000 次 122
  2. WebSocket 连接

    每秒创建 100 个 websocket 连接,当创建 1 万个 websocket 连接时,资源占用情况如下:

真实案例

某个跑在 laf 上的应用,日活数十万,原来需要 4 个 G 的内存,优化后,内存降至 512 MB 以下,CPU 只需要不到 1 核

附加彩蛋

除此之外,我们还做了不少额外的工作:

  1. 日志支持根据不同 Level,以不同的颜色输出。
  2. 通过重定向自定义依赖安装路径,现在支持安装和内置依赖版本不同的依赖包。
  3. 拦截器现在支持类似 koa 洋葱圈结构的前拦截和后拦截的写法,详情查看 Laf 文档。
  4. ...

总结

通过优化 Laf 运行时,我们在将每个应用的成本降低至原来的 1/10 的同时,还大大提高了性能和稳定性,成功把 Laf 的价格打了下来 ~

开源 Serverless 框架 Laf 性能优化实践的更多相关文章

  1. Go RPC 框架 KiteX 性能优化实践 原创 基础架构团队 字节跳动技术团队 2021-01-18

    Go RPC 框架 KiteX 性能优化实践 原创 基础架构团队 字节跳动技术团队 2021-01-18

  2. Redis各种数据结构性能数据对比和性能优化实践

    很对不起大家,又是一篇乱序的文章,但是满满的干货,来源于实践,相信大家会有所收获.里面穿插一些感悟和生活故事,可以忽略不看.不过听大家普遍的反馈说这是其中最喜欢看的部分,好吧,就当学习之后轻松一下. ...

  3. Hadoop YARN:调度性能优化实践(转)

    https://tech.meituan.com/2019/08/01/hadoop-yarn-scheduling-performance-optimization-practice.html 文章 ...

  4. 让Elasticsearch飞起来!——性能优化实践干货

    原文:让Elasticsearch飞起来!--性能优化实践干货 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog ...

  5. etcd 性能优化实践

    https://mp.weixin.qq.com/s/lD2b-DZyvRJ3qWqmlvHpxg 从零开始入门 K8s | etcd 性能优化实践 原创 陈星宇 阿里巴巴云原生 2019-12-16 ...

  6. 第17 章 : 深入理解 etcd:etcd 性能优化实践

    深入理解 etcd:etcd 性能优化实践 本文将主要分享以下五方面的内容: etcd 前节课程回顾复习: 理解 etcd 性能: etcd 性能优化 -server 端: etcd 性能优化 -cl ...

  7. 直播推流端弱网优化策略 | 直播 SDK 性能优化实践

    弱网优化的场景 网络直播行业经过一年多的快速发展,衍生出了各种各样的玩法.最早的网络直播是主播坐在 PC 前,安装好专业的直播设备(如摄像头和麦克风),然后才能开始直播.后来随着手机性能的提升和直播技 ...

  8. 手游录屏直播技术详解 | 直播 SDK 性能优化实践

    在上期<直播推流端弱网优化策略 >中,我们介绍了直播推流端是如何优化的.本期,将介绍手游直播中录屏的实现方式. 直播经过一年左右的快速发展,衍生出越来越丰富的业务形式,也覆盖越来越广的应用 ...

  9. 转:携程App的网络性能优化实践

    http://kb.cnblogs.com/page/519824/ 携程App的网络性能优化实践 受益匪浅的一篇文章,让我知道网络交互并不是简单的传输和接受数据.真正的难点在于后面的性能优化 下面对 ...

  10. Lazy<T>在Entity Framework中的性能优化实践

    Lazy<T>在Entity Framework中的性能优化实践(附源码) 2013-10-27 18:12 by JustRun, 328 阅读, 4 评论, 收藏, 编辑 在使用EF的 ...

随机推荐

  1. Redis从入门到放弃(11):雪崩、击穿、穿透

    1.前言 Redis作为一款高性能的缓存数据库,为许多应用提供了快速的数据访问和存储能力.然而,在使用Redis时,我们不可避免地会面对一些常见的问题,如缓存雪崩.缓存穿透和缓存击穿.本文将深入探讨这 ...

  2. 《SQL与数据库基础》23. 读写分离

    目录 读写分离 一主一从 准备 配置 双主双从 准备 配置 主库配置 从库配置 从库关联主库 主库相互复制 双主双从读写分离 本文以 MySQL 为例.以 MyCat 数据库中间件为例,通过 MyCa ...

  3. 图解Spark Graphx基于connectedComponents函数实现连通图底层原理

    原创/朱季谦 第一次写这么长的graphx源码解读,还是比较晦涩,有较多不足之处,争取改进. 一.连通图说明 连通图是指图中的任意两个顶点之间都存在路径相连而组成的一个子图. 用一个图来说明,例如,下 ...

  4. 为何 Linus 一个人就能写出这么强的系统,中国却做不出来?

    前言 知乎上有一个提问:为何 Linus 一个人就能写出这么强的系统,中国却做不出来? ↓↓↓ 今天,我们就这个话题,一起来做个讨论. 不知道大家是怎么看这个问题的?是美国人更聪明吗,所以才能写出这么 ...

  5. word2010中统一调整表格格式

    word中统一调整表格格式基本思路是: 1.选中所有的表格. 2.再对表格格式调整.    选中所有表格需要用到宏,操作很简单,具体操作如下: (1)工具栏"视图"下右下角&quo ...

  6. 让你的HpSocket Pull支持同步(应答式)操作

    什么是HPSocket HP-Socket 是一套通用的高性能 TCP/UDP 通信框架,包含服务端组件.客户端组件和Agent组件,广泛适用于各种不同应用场景的 TCP/UDP 通信系统,提供 C/ ...

  7. SQL - 5

    Smiling & Weeping ----我本不想和风讨论你,可风说可以替我去见你 第五章:SQL高级处理 5.1 窗口函数 5.1.1 窗口函数概念及基本的使用方法 窗口函数也称为OLAP ...

  8. 从0到1实现 OpenTiny 组件库跨框架技术

    本文分享自华为云社区<从0到1实现 OpenTiny 组件库跨框架技术>,作者:华为云社区精选 . 在华为云<DTSE Tech Talk>技术直播第44期<0基础玩转 ...

  9. Chromium Mojo通信

    Mojo 是一个跨平台 IPC 框架,它诞生于 chromium ,用来实现 chromium 进程内/进程间的通信.目前,它也被用于 ChromeOS. 在我们代码应用中,如何使用Mojo来作进程间 ...

  10. linux 批量替换文件内容

    1.批量查找某个目下文件的包含的内容,例如: #   grep -rn "要找查找的文本" ./ 2.批量查找并替换文件内容. #   sed -i "s/要找查找的文本 ...