如何在 Node.js 中 ”相对可靠” 的高效执行可信三方的代码

简介: 如何在 Node.js 中 ”相对可靠” 的高效执行可信三方的代码,一起来探讨一下。

作者 | 淘系-霸剑

image.png在开始正文之前,解释下标题,以免给读者们带来误解。

可靠:一方面是单次执行维度的安全可信,另一方面是多次执行可能带来的内存泄漏等问题。

可信三方:公司内部的其他团队、经过审核的外包开发代码和久经考验的开源社区库(锁定版本)。

相对可靠:具备持续稳定运行的能力,特别注意这里不包括安全相关的考虑。在本文的最后也会扩展介绍一些与安全相关的东西。

什么样的场景有这样的需求?

常见的有:

  1. SSR(组件的代码需要在服务端执行)
  2. 分布式定时任务系统(内部用户提交的轻量级代码)
  3. 规则引擎(匹配条件等的代码)

等等。

在可信三方的条件下使用容器隔离等技术会带来较大的 overhead,影响性能。

要解决哪些问题?

典型问题:

  1. 内存泄露(变量隔离)
  2. CPU 时间限制(死循环、长时间运行的代码)
  3. 外部资源控制

Node.js 的代码执行环境模型

image.png

我们都知道 Node.js 是单进程且默认情况下是只有一个 V8 执行线程的,但是这是由什么决定的呢?

从上图中可以看到几个概念,这里先解释一下:

isolate :顾名思义就是一个独立的世界。一个 isolate 就是一个独立的 V8 实例,其中包括了内存管理、GC 收集器等等。isolate 和实际的一个 OS Thread 成绑定关系。

context:一个 isolate 并不足以执行你的代码,我们可以看到在 Node.js 中有 global,在浏览器中有 windowcontext 可以指代它们。实际上 context 就是在一个 isolate 的堆上定义的一个全局对象,并且在一个 isolate 中可以存在很多个 context,它们互相之间可以安全的访问。

具体的方案

从上一节中我们可以根据不同的隔离级别找到不同的方案。

new Function

const func = new Function(`console.log('hello')`).bind({});

这种方式在前端动态加载代码的时候比较常用,优势是速度较快,函数中所操作的局部变量会局限在 func 的作用域中。缺陷比较明显,如果加载的代码中使用

const global = Function('return this')();

就可以很方便的逃逸出去了

> new Function('const global = Function("return this")(); return {global, a: this};').bind({})()
{
global: Object <a href="https://nodejs.org/api/vm.html">global] {
global: [Circular],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(util.promisify.custom)]: [Function]
},
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(util.promisify.custom)]: [Function]
}
},
a: {}
}

这种方案的问题在于寄希望于通过函数的 scope 来做到变量的隔离。

Node.js VM

API 文档VM 是一个在 Node.js 0.3.0 出现的模块,相对于new Function 能做到更好的变量(内存)隔离。

const vm = require('vm');
const script = new vm.Script('globalVar = "set"');
const contexts = <a href="https://nodejs.org/api/worker_threads.html">{}, {}, {}];
contexts.forEach((context) => {
script.runInNewContext(context);
});
console.log(contexts);
// Prints: [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]

从 API 命名中就可以看到,VM 模块可以创建 context ,让代码在非 Node.js Main Context 中执行,避免了类似new Function 的变量逃逸。

缺点在于 context 的创建是一个相对慢的过程,对于需要频繁执行的代码,每次创建 context 对性能的影响比较大。

const vm = require('vm');
const suite = new (require('benchmark').Suite);
const code = 'var square = n * n;'
const fn = new Function('n', code);
const script = vm.createScript(code);
const n = 5;
const contextObj = { n };
const context = vm.createContext(contextObj);
console.log(process.version);
suite.add('vm.runInNewContext', function() {
vm.runInNewContext(code, { n });
})
.add('script.runInNewContext', function() {
script.runInNewContext({ n });
})
.add('script.runInContext', function() {
script.runInContext(context);
})
.add('new Function', function() {
fn(n);
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({ 'async': true });
/**
v14.13.0
vm.runInNewContext x 2,332 ops/sec ±2.85% (77 runs sampled)
script.runInNewContext x 2,609 ops/sec ±2.84% (86 runs sampled)
script.runInContext x 571,992 ops/sec ±0.93% (89 runs sampled)
new Function x 826,079,093 ops/sec ±1.61% (81 runs sampled)
Fastest is new Function
**/

VM 模块在基本的 context 之外还提供了更多的功能,比如timeoutmicrotaskMode 的设置。但是这里的控制在最坏的情况下会把 Nodejs MainContext 卡住timeout 时长的时间,导致应用执行出现卡死,依然不是好的方案。

Node.js Worker Threads


WorkerThread 是在 Node.js 10.x 出现的模块,它通过创建多个 isolate 做到了多线程执行代码,我们也可以使用它结合 Node.js VM 来做到在 Node.js 进程内最完整的隔离与控制。

缺点在于 Worker Threads 的创建是非常慢的,实际使用时需要常驻,并且主线程与 Worker Threads 之间只能通过 IPC 传递满足 HTML structured clone algorithm 的数据结构。

总结

方案 变量隔离 内存限制 isolated 隔离 执行时间限制 异步操作限制
new Function 部分隔离
vm
workerthreads

社区中基于 isolate 隔离的实现还有 isolated-vm 等,可以进一步参考。读者如果有更好的思路可以跟笔者探讨交流,非常感谢。

扩展:安全

上述的方案均不能实现面向恶意代码的安全,针对安全方面的要求笔者倾向的是两个级别的方案:可信容器以及 WebAssembly 容器。

可信容器


一类是基于在已有的成熟 VMM,进行裁剪,通过虚拟化技术解决安全隔离性问题,比如 AWS 的 Firecracker。

而另一类则完全不使用虚拟机,以 Google 的 gVisor 以及一些 Unikernel 技术为代表。将虚拟化的边界移到了系统调用层面。

基于容器方案的对性能都或多或少有一定影响,且业务应用开发使用的门槛较高,如果不从 Infra 级别上就支持很难以应用到业务应用开发中。

WebAssembly 容器


这是笔者比较看好的方向,通过将代码编译到 WebAssembly 的方式,WebAssembly 容器既可以限制指令执行速度也可以限制内存、通过 WASI 限制访问外部资源等。缺点是目前没有成熟的方案,V8 对 WebAssembly 的支持主要聚焦在了执行性能上,对管控等还没有更好的支持。

目录
打赏
0
0
0
0
226
分享
相关文章
【Javascript系列】Terser除了压缩代码之外,还有优化代码的功能
Terser 是一款广泛应用于前端开发的 JavaScript 解析器和压缩工具,常被视为 Uglify-es 的替代品。它不仅能高效压缩代码体积,还能优化代码逻辑,提升可靠性。例如,在调试中发现,Terser 压缩后的代码对删除功能确认框逻辑进行了优化。常用参数包括 `compress`(启用压缩)、`mangle`(变量名混淆)和 `output`(输出配置)。更多高级用法可参考官方文档。
53 11
JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
Array.sort() 是一个功能强大的方法,通过自定义的比较函数,可以处理各种复杂的排序逻辑。无论是简单的数字排序,还是多字段、嵌套对象、分组排序等高级应用,Array.sort() 都能胜任。同时,通过性能优化技巧(如映射排序)和结合其他数组方法(如 reduce),Array.sort() 可以用来实现高效的数据处理逻辑。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
array.map()可以用来数据转换、创建派生数组、应用函数、链式调用、异步数据流处理、复杂API请求梳理、提供DOM操作、用来搜索和过滤等,比for好用太多了,主要是写法简单,并且非常直观,并且能提升代码的可读性,也就提升了Long Term代码的可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
【通义灵码】三句话生成P5.js粒子特效代码,人人都可以做交互式数字艺术
我发掘出的通义灵码AI程序员新玩法:三句话生成P5.js粒子特效代码,人人都可以做交互式数字艺术
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
161 21
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
三句话生成 P5.js 粒子特效代码,人人都可以做交互式数字艺术
短短几分钟,两个完全不懂P5.js的人类,和通义灵码AI程序员一起,共同完成了有真实物理引擎和碰撞检测的3D仿真动画。人类扮演的角色更像产品经理和架构师,提出开发需求和迭代修改方案,而AI的作用更像码农,任劳任怨,熟练用各种编程语言完成技术底层的脏活累活。这只是AI编程的冰山一角,未来,每一个艺术家都能快速做出自己的创意原型,每一个数学老师都能轻松做出自己的教学动画。
20 个 JavaScript 简化技巧,让你的代码更上一层楼!
JavaScript 既灵活又强大,掌握以下20个技巧可助你编写更简洁高效的代码
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
78 14
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理;js数组元素检查的方法,some()的使用详解,array.some与array.every的区别(附实际应用代码)
array.some()可以用来权限检查、表单验证、库存管理、内容审查和数据处理等数据校验工作,核心在于利用其短路机制,速度更快,节约性能。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
通过array.every()实现数据验证、权限检查和一致性检查;js数组元素检查的方法,every()的使用详解,array.some与array.every的区别(附实际应用代码)
array.every()可以用来数据验证、权限检查、一致性检查等数据校验工作,核心在于利用其短路机制,速度更快,节约性能。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

热门文章

最新文章