阿里巴巴 Noslate 正式开源 - 面向云原生的 JavaScript 容器方案

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 继 2019 年开源 Midway 框架之后,阿里一直在 Node.js 的前沿进行深度研究,除了加入 TC39 参与标准化建设,向上游 Node.js 项目持续贡献,与龙蜥社区合作优化之外,也在 Serverless 领域有了不小的成果。

今天,向大家介绍我们最新的面向云原生场景,面向 Serverless 架构下的新产品, 代号  Noslate。

Noslate 是什么?

欢迎访问项目了解更多内容:https://github.com/noslate-project/noslate

image.png

JavaScript 是开发者数量最庞大的编程语言,早些年 Node.js 等技术的出现,让 JavaScript 可以轻松地处理各类服务端任务。

但随着云原生/Serverless 等新架构概念的引导,弹性效率成为了全新的架构设计目标。为了能够让 JavaScript 任务拥有更高的弹性效率,进而满足在泛终端、全栈交付等领域的效率期待。我们逐步深入探索的过程中逐渐形成了 Noslate Project,旨在提升云原生场景下 JavaScript 的被调度性能,解决诊断性黑盒问题。

Noslate 它主要由三个子项目组成,分别体现了我们在提升 Javascript 任务弹性效率过程中碰到的问题以及解决方式:

  1. Node.js Distribution:初期针对函数计算冷启动场景优化,降低 Node.js 用户代码加载耗时,形成了针对性的 Node.js 发行版本。
  2. Noslate Workers:随着探索的深入,我们设计了面向轻量端云同构场景的 W3C Web-interoperable JavaScript 轻量化容器方案。在交付灵活度上和资源、执行效率上形成平衡,现在主要应用于中心化的 SSR 渲染等轻量任务场景,效果显著。
  3. Noslate Debugger:在落地业务过程中,我们发现在弹性效率提升后,对于异常和崩溃变得难以定位,得益于 Linux 系统 Coredump 机制的启发,我们设计了基于 Corefile 对问题进行离线诊断的 Noslate Debugger 产品,帮助用户实现问题的回溯和实时定位。

简而言之,Noslate 目标是通过提供完整的技术产品方案, 将 JavaScript 打造成云原生时代最灵活的交付语言。

为什么开源?

一方面我们希望通过开源加强项目产品化程度;另一方面希望在社区中吸收更多的实践场景进而继续完善产品设计,也欢迎大家参与到项目中来。

同时,依托阿里云龙蜥社区和 Anolis 操作系统的合作关系,我们得以在底层探索,实现技术的演进。

一、Noslate Workers

image.png

与传统的 FaaS 架构不同是,这是一个在普通应用容器之上的轻量任务单位。得益于良好的动态任务高密度混部和隔离特性、以及基于任务状态拷贝 API 带来的近乎 0 冷启动特性,可以实现任务的即用即启与即停即抛,进而无需关心在整个大集群中任务节点的编排问题。

与既有架构的关系:

image.png

Noslate Workers 由两个主要组件组成:

  1. Aworker - 轻量、Web-interoperable JavaScript Runtime
  2. Noslated - Servlerss 化的 Aworker 调度管控实现

关于 Aworker

提供 Web API 标准的 Web-interoperable JavaScript 运行时,适合不直接依赖系统接口的业务逻辑部署。Aworker 实现了近似 Service Worker API 的规范,提供了基本的 Request-Response 服务 API。

因为提供了相比于 Node.js 的 API 更加高层次、抽象的定义,不会泄漏系统底层状态,Aworker 通过 Startup Snapshot 和 Warmfork 能力, 实现了更快的水平及垂直扩容,能够在毫秒级启动并处理流量,具备更高的弹性效率。

亮点特性一:Warmfork

熟悉 Linux 系统编程的同学都知道 fork(2) 系统调用有几个优势:

  1. 新进程可以继承母进程的当前状态,而无需从 main() 开始初始化;
  2. pcb、栈、内存页,页表都是纯内存复制,所以进程创建快 (<1ms);
  3. CopyOnWrite,新进程可以继承母进程的静态页表,可节省系统内存;

对于 Node.js 来说,因为其无法在主线程持有所有多线程的状态 (如锁,信号量等),所以 Node.js 进行 fork 的修改难度很大。其多线程设计主要 来源于 libuv 库和 V8 Platform Worker 线程:

  1. 因部分 IO 操作存在同步调用,如 dns,文件读写等,所以 libuv 使用 IO 线程将同步操作转换成异步操作;
  2. Node.js 的 V8 默认配置为多线程 GC、Background Compilation/Optimization 的方式;

Node.js 的单进程多线程模型可以由下图表示:

image.png

Aworker 的设计是采用单进程单线程的模型,也就是将上述模型中的 worker thread 单独抽出放到一独立进程中。Worker 因此可支持 fork,从而避免从 main() 开始的启动消耗,达到快速启动的目的。

image.png

为了支持单线程,Aworker 还做了如下修改:

  1. 使用了 Linux AIO 特性替掉了 libuv 中同步的文件系统操作(不是 POSIX AIO,两者有区别。Posix AIO 类似于 libuv 现有的实现);
  2. 使用 V8 的 SingleThread 模式,这是一个给低端设备(Low-end devices)实现的能力,不过非常符合 Serverless 资源模型;

而为了管理、隔离这些工作进程,我们需要一个轻量的业务进程容器管理组件 Turf ,该组件用于能通过 Warmfork 方式创建新的 Aworker 服务进程,并能提供一定的资源、环境的隔离能力,同时兼容 OCI。区别于传统 runc, rund 的容器,turf 旨在承载如 Aworker 这类轻 JS Runtime,它无需镜像运行,开销更低,可以支持更高的部署密度。

Alinode Warmfork 具体的对比:

image.png

提供 "被复制" 的进程,称为 "种子进程",其他服务进程都是该进程的克隆。譬如 Aworker 作为种子进程,它需要确定自己一个 "可被克隆" 的时间点,将自己的所在状态(内存)作为克隆进程的初始状态。

Warmfork 的系统时序如下:

image.png

亮点特性二:Startup Snapshot

Warmfork 能解决了单机上服务进程的快速启动,对于冷机启动需要采用 Startup Snapshot 方案。Startup Snapshot 和 CodeCache 的区别在于 Startup Snapshot 能够保存用户代码逻辑执行状态,而 CodeCache 只保存代码解析结果、仍然需要重新执行 用户代码逻辑。

设计上,Startup Snapshot 可提供携带用户代码逻辑的快速恢复,但是也有局限性:

  1. Startup Snapshot 对内存开销敏感,如果应用启动阶段用了大量内存,可能造成负优化;
  2. 用户代码启动需要没有歧义的状态,比如 IP 地址、日期、连接状态、服务发现结果等,针对这些歧义内容用户代码需要在进程恢复时有修正能力;

V8 的 Startup Snapshot Serializer 就是一个类似于 GC 的对象遍历器。这个遍历器通过遍历加入到 Snapshot 中的 Root 对象,遍历它所对应的对象图并按照对象关系生成一系列的反序列化指令。

Startup Snapshot 相当于从 V8 Context 对象与它的 globalThis 开始,遍历堆中所有的对象并将对象关系与引用序列化成 特有的字节码,形成一个线性的可存储状态。并在恢复时,解释执行这些字节码,恢复堆中的对象内容与他们之间的引用关系。

image.png

上述的两类和被调度性能相关的特性被统一归类为状态拷贝 API,具体使用可以参考官网文档中的《状态拷贝 API》章节[1],详细介绍了命令行参数和程序内的 Events。https://noslate.midwayjs.org/docs/noslate_workers/aworker/serialize-api/

Noslated

Noslate Container Deamon,作为 Noslate Workers 解决方案的核心管控程序,提供了实例调度、弹性扩缩容、配置管理、流量管理等能力。

基于健壮性考虑,它由两个角色组成:控制面(Control Plane)、数据面(Data Plane)

image.png

Noslated 对于实例的管控主要有三个模式:

  1. 基础模式 - 基于流量的扩缩容
  2. 即抛模式 - 运行完即销毁
  3. 预留模式 - 面向历史场景兼容,在此不额外展开,详情可以查阅官网【预留策略】。

1、基础模式

当流量进入 Data Plane 后,如果没有能够处理请求的 Worker 实例,会通过 requestQueueing 事件通知 Control Plane,它会根据当前水位决定扩容数量,如果当前已无法创建 Worker 实例,会返回资源上限报错。新的 Worker 实例启动后,会自动连接到 Data Plane,Data Plane 发现新的 Worker 实例连接后会主动触发初始化请求,初始化成功后开始消费请求队列里堆积的请求。

当 Worker 实例闲置一段时间后,Control Plane 会主动发起 GC 操作,告知 Data Plane 关闭流量,流量关闭后,Control Plane 会通知 Turf 关闭 Worker 实例,清理资源残留。

image.png

2、即抛模式

针对特定的灵活场景,一次性的轻量用户脚本执行(比如特别高密度的混部执行二方任务如 SSR),为了隔离不同请求间的上下文,可以针对每个请求创建一个实例,并在执行后销毁。

image.png

比如普通的 Node.js 实例带上业务逻辑启动一般都不会太快,如果直接用了响应用户流量会难以接受。得益于 Aworker 运行时并配合 Warmfork [2]以及 Startup Snapshot [3]能力,更快完成 Worker 实例启动。亦可把一部分业务自己的初始化逻辑放置到 Warmfrok 特性中,进而让新实例都是最少的初始化时间,这才让高密度混部二方任务成为可能。

3、预留模式

在此不额外展开,详情可以查阅官网【预留策略】[4]

二、Noslate Debugger

image.png

Noslate Debugger 是针对 V8 应用的离线分析工具,它可以分析 Node.js 等应用程序产生的 Corefile (Core 文件):

  1. 检查 Node.js/V8 应用程序的结构体、堆栈等内容
  2. 检查 V8 堆内的各种对象信息
  3. 从 Corefile 中导出 Heap Snapshot
  4. 业务无感获取 Corefile (通过 Arthur 工具)
  5. 已支持 Node.js / AWorker LTS 官方发行版

为了更好的解决问题而不是造轮子,在未来的几个月 Noslate Debugger 也会和国内社区 Node.js 稳定性领域优秀的开源软件 Easy Monitor 共建整合,在 Node.js/V8 的问题诊断领域形成合力,也是值得期待的事情。

优点一:基于 Corefile 的 "快照" 更适应 Serverless

Serverless 应用通常会使用大量生命周期短、规格小的任务实例,但在此类任务实例上获得调试诊断能力并不容易,这使得 Serverless 应用长期处于较为黑盒的窘境。比如,Inspector 需要稳定和长时的网络连接、运行时 Heap Snapshot 需要较多的计算和内存资源,都是和 Serverless 架构背道而驰的。

不管是 V8 的对象还是堆快照,它都是 "信息" 在内存中的存储,而 Inspector 功能就是可以在 "运行时" 能提取这些信息。Noslate Debugger 通过 Corefile 将这部分调试诊断能力转移到了离线时进行,让原有实时性要求高的在线诊断调试转化为只需简单文件上传即可集成使用。

在用户本地或云端服务上提供接近用户本地开发时的调试诊断体感:

image.png

Corefile (特指 GNU Corefile 格式) 主要记录的是 Node.js 进程的内存和寄存器转储(CoreDump: 内存到磁盘的过程)。所以它也是进程完整“信息”,被用于 Linux 系统应用 Crash(有损) 的调试载体,也可用于 GCore(无损) 产生进程快照用于离线分析。

优点二:更小的业务影响

对比原有线上 "堆快照" 对业务的影响长达数分钟,到只影响业务 RT 秒级(通过 GCore),甚至只有几十毫秒 (通过 Arthur 工具)。Corefile 快照也不会有任何运行时的"添油加醋",所以它也适合那些还未被GC的对象定位,譬如诊断已经结束了的业务处理等。

Arthur 是 Noslate Debugger 中用于低影响获取 core文件的工具,利用 fork 减少进程暂停时间,LZ4 压缩减少转储体积。带业务流量的线上环境抓取,业务影响 31.106 毫秒,Corefile 大小为 338 MB (进程原使用 1.44GB 物理内存)。

三、Node.js 发行版

image.png

我们还对 Node.js 的实例进行了定向弹性场景的优化,提高了用户代码的加载速度,从而降低了冷启动时间。主要包括 Require 关系加速、Bytecode Cache,优化效果提升可高达 100% ~ 200%。该发行版,同时包含来自阿里云基础软件团队在 ARM 架构的性能优化特性。

冷启动优化

PGO(Profile Guided Optimization),是一种根据运行时 Profiling Data 来进行编译优化的技术,这里我们借鉴了这一概念。主要是通过执行一遍之后收集启动阶段的热点数据生成缓存文件,后续通过内存映射直接加载高效的缓存文件,即可获得提升在 100% ~ 200% 的用户代码冷启动优化效果。

image.png

面向特定平台架构优化

Node.js 支持包括 x64、arm64 等在内的多种架构。但针对 ARM 芯片的快速发展,上游版本往往仅提供基础适配,缺少针对新指令集的优化,导致在 ARM 芯片上无法获得潜在的性能提升。当下主流云厂商大都提供了 ARM 架构、高性价比的运行环境。Noslate Node.js 发行版针对 ARM 等平台的优化可以让应用在这些架构上获得更高的性能和效率。目前 Noslate Node.js 发行版已经在进行针对阿里云 Ampere、阿里云倚天的定制优化,未来计划包括支持龙蜥社区中的其他架构。主要包括:zlib 特性优化、其他一些利用 SIMD 的性能提升都在 PR 合并和准别中。

详细了解

上面是对 Noslate Project 的简单介绍,如果想要详细了解可通过下述方式:

致谢

感谢阿里巴巴集团内业务方的支持,同时还要特别感谢所有给本项目贡献过代码、一起探索过技术方向伙伴们(包括不限于 legendecas、mariodu、zhaolei0505、XadillaX、umuoy1、oraluben、hyj1991 等)。

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
1月前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
95 2
|
1月前
|
运维 Cloud Native 虚拟化
一文吃透云原生 Docker 容器,建议收藏!
本文深入解析云原生Docker容器技术,涵盖容器与Docker的概念、优势、架构设计及应用场景等,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
一文吃透云原生 Docker 容器,建议收藏!
|
15天前
|
Kubernetes Cloud Native 微服务
探索云原生技术:容器化与微服务架构的融合之旅
本文将带领读者深入了解云原生技术的核心概念,特别是容器化和微服务架构如何相辅相成,共同构建现代软件系统。我们将通过实际代码示例,探讨如何在云平台上部署和管理微服务,以及如何使用容器编排工具来自动化这一过程。文章旨在为开发者和技术决策者提供实用的指导,帮助他们在云原生时代中更好地设计、部署和维护应用。
|
1月前
|
运维 Kubernetes Cloud Native
云原生技术:容器化与微服务架构的完美结合
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术以其灵活性和高效性成为企业的新宠。本文将深入探讨云原生的核心概念,包括容器化技术和微服务架构,以及它们如何共同推动现代应用的发展。我们将通过实际代码示例,展示如何在Kubernetes集群上部署一个简单的微服务,揭示云原生技术的强大能力和未来潜力。
|
17天前
|
JavaScript 前端开发 测试技术
在 golang 中执行 javascript 代码的方案详解
本文介绍了在 Golang 中执行 JavaScript 代码的四种方法:使用 `otto` 和 `goja` 嵌入式 JavaScript 引擎、通过 `os/exec` 调用 Node.js 外部进程以及使用 WebView 嵌入浏览器。每种方法都有其适用场景,如嵌入简单脚本、运行复杂 Node.js 脚本或在桌面应用中显示 Web 内容。
51 15
在 golang 中执行 javascript 代码的方案详解
|
10天前
|
存储 网络架构
Next.js 实战 (四):i18n 国际化的最优方案实践
这篇文章介绍了Next.js国际化方案,作者对比了网上常见的方案并提出了自己的需求:不破坏应用程序的目录结构和路由。文章推荐使用next-intl库来实现国际化,并提供了详细的安装步骤和代码示例。作者实现了国际化切换时不改变路由,并把当前语言的key存储到浏览器cookie中,使得刷新浏览器后语言不会失效。最后,文章总结了这种国际化方案的优势,并提供Github仓库链接供读者参考。
|
16天前
|
Kubernetes Cloud Native Docker
云原生之旅:从容器化到微服务
本文将带领读者踏上云原生的旅程,深入探讨容器化和微服务架构的概念、优势以及它们如何共同推动现代软件的发展。我们将通过实际代码示例,展示如何在Kubernetes集群上部署一个简单的微服务应用,并解释相关的配置和操作。无论你是云原生新手还是希望深化理解,这篇文章都将为你提供有价值的见解和实操指南。
|
27天前
|
Kubernetes Cloud Native Docker
云原生之旅:从传统架构到容器化服务的演变
随着技术的快速发展,云计算已经从简单的虚拟化服务演进到了更加灵活和高效的云原生时代。本文将带你了解云原生的概念、优势以及如何通过容器化技术实现应用的快速部署和扩展。我们将以一个简单的Python Web应用为例,展示如何利用Docker容器进行打包和部署,进而探索Kubernetes如何管理这些容器,确保服务的高可用性和弹性伸缩。
|
22天前
|
Kubernetes Cloud Native 开发者
云原生入门:从容器到微服务
本文将带你走进云原生的世界,从容器技术开始,逐步深入到微服务架构。我们将通过实际代码示例,展示如何利用云原生技术构建和部署应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和启示。
|
28天前
|
运维 Cloud Native 云计算
云原生之旅:Docker容器化实战
本文将带你走进云原生的世界,深入理解Docker技术如何改变应用部署与运维。我们将通过实际案例,展示如何利用Docker简化开发流程,提升应用的可移植性和伸缩性。文章不仅介绍基础概念,还提供操作指南和最佳实践,帮助你快速上手Docker,开启云原生的第一步。
下一篇
DataWorks