Node.js架构剖析

一.Node.js 缔造的传奇
I have a job now, and this guy is the reason why I have that now. His hobby project is what I use for living. Thanks. —— Shajan Jacob2009 年 Ryan Dahl 在 JSConf EU 大会上推出了 Node.js,最初是希望能够通过异步模型突破传统 Web 服务器的高并发瓶颈,之后愈渐发展成熟,应用越来越广,出现了繁荣的 Node.js 生态
借助 Node.js 走出浏览器之后,JAVAScript 语言也 一发不可收拾 :
Any Application that can be written in JavaScript, will eventually be written in JavaScript. —— Jeff Atwood(摘自 The Principle of Least Power )
早在 2017 年,NPM 就凭借茫茫多的社区模块成为了 世界上最大的 package registry ,目前模块数量已经 超过 125 万 ,并且仍在快速增长中(每天新增900多个)
甚至 Node.js 工程师已经成为了一种新兴职业,那么,带有传奇色彩的 Node.js 本身是怎么实现的呢?
二.Node.js 架构概览

Node.js架构剖析

文章插图
 
JS 代码跑在 V8 引擎上,Node.js 内置的 fs 、 http 等核心模块通过 C++ Bindings 调用 libuv、c-ares、llhttp 等 C/C++类库,从而接入操作系统提供的平台能力
其中,最重要的部分是 V8 和 libuv
三.源码依赖V8V8 is google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others.一个用 C++写的 JavaScript 引擎,由 Google 维护,用于 Chrome 浏览器和 Node.js
libuvlibuv is cross-platform support library which was originally written for Node.js. It’s designed around the event-driven asynchronous I/O model.为 Node.js 量身打造,用 C 写的跨平台异步 I/O 库,提供了非阻塞的文件系统、DNS、网络、子进程、管道、信号、轮询和流式处理机制:
Node.js架构剖析

文章插图
 
对于无法在操作系统层面异步去做的工作,通过线程池来完成,如文件 I/O、DNS 查询等,具体原因见 Complexities in File I/O
P.S.线程池的容量可以配置,默认是 4 个线程,具体见 Thread pool work scheduling
此外,Node.js 中的事件循环、事件队列也都是由 libuv 提供的 :
Libuv provides the entire event loop functionality to NodeJS including the event queuing mechanism.具体运作机制如下图:
Node.js架构剖析

文章插图
 
其它依赖库另外,还依赖一些 C/C++库:
  • llhttp :用 TypeScript 和 C 写的轻量级 HTTP 解析库,比之前的 http_parser 快 1.5 倍,不含任何系统调用和内存分配(也不缓存数据),因此每个请求的内存占用极小
  • c-ares :一个 C 库,用来处理异步的 DNS 请求,对应 Node.js 中 dns 模块提供的 resolve() 系列方法
  • OpenSSL :一个通用的加密库,多用于网络传输中的 TLS 和 SSL 协议实现,对应 Node.js 中的 tls 、 crypto 模块
  • zlib :提供快速压缩和解压支持
P.S.关于 Node.js 源码依赖的更多信息,见 Dependencies
四.核心模块像浏览器提供的 DOM/BOM API 一样,Node.js 不仅提供了 JavaScript 运行时环境,还扩展出了一系列平台 API,例如:
  • 文件系统相关:对应 fs 模块
  • HTTP 通信:对应 http 模块
  • 操作系统相关:对应 os 模块
  • 多进程:对应 child_process 、 cluster 模块
【Node.js架构剖析】这些内置模块称为 核心模块,为迈出浏览器世界的 JavaScript 长上了手脚
五.C++ Bindings在核心模块之下,有一层 C++ Bindings,将上层的 JavaScript 代码与下层 C/C++类库桥接起来
底层模块为了更好的性能,采用 C/C++实现,而上层的 JavaScript 代码无法直接与 C/C++通信,因而需要一个桥梁(即 Binding):
Bindings, as the name implies, are glue codes that “bind” one language with another so that they can talk with each other. In this case (Node.js), bindings simply expose core Node.js internal libraries written in C/C++ (c-ares, zlib, OpenSSL, llhttp, etc.) to JavaScript.另一方面,通过 Bindings 也可以复用可靠的老牌开源类库,而不必手搓所有底层模块
以文件 I/O 为例,读取当前 JS 文件内容并输出到标准输出:
// readThisFile.jsconst fs = require('fs')const path = require('path')const filePath = path.resolve(__filename);// Parses the buffer into a stringfunction callback (data) {return data.toString()}// Transforms the function into a promiseconst readFileAsync = (filePath) => {return new Promise((resolve, reject) => {fs.readFile(filePath, (err, data) => {if (err) return reject(err)return resolve(callback(data))})})}(() => {readFileAsync(filePath).then(console.log).catch(console.error)})()


推荐阅读