您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

Node.js架构剖析

时间:2020-04-30 13:22:30  来源:  作者:

一.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 Jacob

2009 年 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

三.源码依赖

V8

V8 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

libuv

libuv 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 模块

这些内置模块称为 核心模块,为迈出浏览器世界的 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.js
const fs = require('fs')
const path = require('path')
const filePath = path.resolve(__filename);

// Parses the buffer into a string
function callback (data) {
  return data.toString()
}

// Transforms the function into a promise
const 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)
})()

然而,其中用到的 fs.readFile 接口既不是 V8 提供的,也不是 JS 自带的,而是由 Node.js 以 C++ Binding 的形式借助 libuv 实现的:

// https://github.com/nodejs/node/blob/v14.0.0/lib/fs.js#L58
const binding = internalBinding('fs');
// https://github.com/nodejs/node/blob/v14.0.0/lib/fs.js#L71
const { FSReqCallback, statValues } = binding;

// https://github.com/nodejs/node/blob/v14.0.0/lib/fs.js#L297
function readFile(path, options, callback) {
  callback = maybeCallback(callback || options);
  options = getOptions(options, { flag: 'r' });
  if (!ReadFileContext)
    ReadFileContext = require('internal/fs/read_file_context');
  const context = new ReadFileContext(callback, options.encoding);
  context.isUserFd = isFd(path); // File descriptor ownership

  const req = new FSReqCallback();
  req.context = context;
  req.oncomplete = readFileAfterOpen;

  if (context.isUserFd) {
    process.nextTick(function tick() {
      req.oncomplete(null, path);
    });
    return;
  }

  path = getValidatedPath(path);
  const flagsNumber = stringToFlags(options.flags);
  binding.open(pathModule.toNamespacedPath(path),
              flagsNumber,
              0o666,
              req);
}

最后的 binding.open 是一个 C++调用,用来打开文件描述符,三个参数分别是文件路径, C++ fopen 的文件访问模式串(如 r 、 w+ ),以及八进制格式的文件读写权限( 666 表示每个人都有读写权限),和接收返回数据的 req 回调

其中, internalBinding 是个 C++ binding loader, internalBinding('fs') 实际加载的 C++代码位于 node/src/node_file.cc

至此,关键的部分差不多都清楚了,那么,一段 Node.js 代码究竟是怎样运行的呢?

六.运行原理

首先,编写的 JavaScript 代码由 V8 引擎来运行,运行中注册的事件监听会被保留下来,在对应的事件发生时收到通知

网络、文件 I/O 等事件产生时,已注册的回调函数将排到事件队列中,接着被事件循环取出放到调用栈上,回调函数执行完(调用栈清空)之后,事件循环再取一个放上去……

执行过程中遇到 I/O 操作就交给 libuv 线程池中的某个 woker 来处理,结束之后 libuv 产生一个事件放入事件队列。事件循环处理到返回事件时,对应的回调函数才在主线程开始执行,主线程在此期间继续其它工作,而不阻塞等待

Node.js 就像一家咖啡馆,店里只有一个跑堂的(主线程),一大堆顾客涌过来的时候,会排队等候(进入事件队列),到号的顾客订单会被传给经理(libuv),经理将订单分配给咖啡师(worker 线程),咖啡师用不同的原料和工具(底层依赖的 C/C++模块)来制作订单要求的各种咖啡,一般会有 4 个咖啡师值班,高峰时候可能会增加一些。订单传给经理后,不等咖啡做出来,而是接着处理下一个订单。一杯咖啡做完之后,放到出餐流水线(IO Events 队列),送达前台后,跑堂的喊名字,顾客过来取



Tags:Node.js架构   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
一.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...【详细内容】
2020-04-30  Tags: Node.js架构  点击:(61)  评论:(0)  加入收藏
▌简易百科推荐
为了构建高并发、高可用的系统架构,压测、容量预估必不可少,在发现系统瓶颈后,需要有针对性地扩容、优化。结合楼主的经验和知识,本文做一个简单的总结,欢迎探讨。1、QPS保障目标...【详细内容】
2021-12-27  大数据架构师    Tags:架构   点击:(5)  评论:(0)  加入收藏
前言 单片机开发中,我们往往首先接触裸机系统,然后到RTOS,那么它们的软件架构是什么?这是我们开发人员必须认真考虑的问题。在实际项目中,首先选择软件架构是非常重要的,接下来我...【详细内容】
2021-12-23  正点原子原子哥    Tags:架构   点击:(7)  评论:(0)  加入收藏
现有数据架构难以支撑现代化应用的实现。 随着云计算产业的快速崛起,带动着各行各业开始自己的基于云的业务创新和信息架构现代化,云计算的可靠性、灵活性、按需计费的高性价...【详细内容】
2021-12-22    CSDN  Tags:数据架构   点击:(10)  评论:(0)  加入收藏
▶ 企业级项目结构封装释义 如果你刚毕业,作为Java新手程序员进入一家企业,拿到代码之后,你有什么感觉呢?如果你没有听过多模块、分布式这类的概念,那么多半会傻眼。为什么一个项...【详细内容】
2021-12-20  蜗牛学苑    Tags:微服务   点击:(9)  评论:(0)  加入收藏
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  青锋爱编程    Tags:后台架构   点击:(21)  评论:(0)  加入收藏
在了解连接池之前,我们需要对长、短链接建立初步认识。我们都知道,网络通信大部分都是基于TCP/IP协议,数据传输之前,双方通过“三次握手”建立连接,当数据传输完成之后,又通过“四次挥手”释放连接,以下是“三次握手”与“四...【详细内容】
2021-12-14  架构即人生    Tags:连接池   点击:(17)  评论:(0)  加入收藏
随着移动互联网技术的快速发展,在新业务、新领域、新场景的驱动下,基于传统大型机的服务部署方式,不仅难以适应快速增长的业务需求,而且持续耗费高昂的成本,从而使得各大生产厂商...【详细内容】
2021-12-08  架构驿站    Tags:分布式系统   点击:(23)  评论:(0)  加入收藏
本系列为 Netty 学习笔记,本篇介绍总结Java NIO 网络编程。Netty 作为一个异步的、事件驱动的网络应用程序框架,也是基于NIO的客户、服务器端的编程框架。其对 Java NIO 底层...【详细内容】
2021-12-07  大数据架构师    Tags:Netty   点击:(17)  评论:(0)  加入收藏
前面谈过很多关于数字化转型,云原生,微服务方面的文章。虽然自己一直做大集团的SOA集成平台咨询规划和建设项目,但是当前传统企业数字化转型,国产化和自主可控,云原生,微服务是不...【详细内容】
2021-12-06  人月聊IT    Tags:架构   点击:(23)  评论:(0)  加入收藏
微服务看似是完美的解决方案。从理论上来说,微服务提高了开发速度,而且还可以单独扩展应用的某个部分。但实际上,微服务带有一定的隐形成本。我认为,没有亲自动手构建微服务的经历,就无法真正了解其复杂性。...【详细内容】
2021-11-26  GreekDataGuy  CSDN  Tags:单体应用   点击:(35)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条