深入理解Node.js-背景了解:核心思想与源码分析【1】

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介:

node背景,了解一下

(1)体系架构

Node.js主要分为四大部分,Node Standard Library,Node Bindings,V8,Libuv,架构图如下:

cmd-markdown-logo

  • Node Standard Library 是我们每天都在用的标准库,如Http, Buffer 模块。
  • Node Bindings 是沟通JS 和 C++的桥梁,封装V8和Libuv的细节,向上层提供基础API服务。
  • 这一层是支撑 Node.js 运行的关键,由 C/C++ 实现。
    • V8 是Google开发的JavaScript引擎,提供JavaScript运行环境,可以说它就是 Node.js 的发动机。
    • Libuv 是专门为Node.js开发的一个封装库,提供跨平台的异步I/O能力
    • C-ares:提供了异步处理 DNS 相关的能力。
    • http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。

代码结构

树形结构查看,使用 tree 命令

  nodejs git:(master) tree -L 1
.
├── AUTHORS
├── BSDmakefile
├── BUILDING.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── COLLABORATOR_GUIDE.md
├── CONTRIBUTING.md
├── GOVERNANCE.md
├── LICENSE
├── Makefile
├── README.md
├── ROADMAP.md
├── WORKING_GROUPS.md
├── android-configure
├── benchmark
├── common.gypi
├── config.gypi
├── config.mk
├── configure
├── deps
├── doc
├── icu_config.gypi
├── lib
├── node.gyp
├── out
├── src
├── test
├── tools
└── vcbuild.bat
复制代码

进一步查看 deps目录:

  nodejs git:(master) tree deps -L 1
deps
├── cares
├── gtest
├── http_parser
├── npm
├── openssl
├── uv
├── v8
└── zlib
复制代码

node.js核心依赖六个第三方模块。其中核心模块 http_parser, uv, v8这三个模块在后续章节我们会陆续展开。 gtest是C/C++单元测试框架。

(2)libuv

node.js最初开始于2009年,是一个可以让Javascript代码离开浏览器的执行环境也可以执行的项目。 node.js使用了Google的V8解析引擎和Marc Lehmann的libev。Node.js将事件驱动的I/O模型与适合该模型的编程语言(Javascript)融合在了一起。随着node.js的日益流行,node.js需要同时支持windows, 但是libev只能在Unix环境下运行。Windows 平台上与kqueue(FreeBSD)或者(e)poll(Linux)等内核事件通知相应的机制是IOCP。libuv提供了一个跨平台的抽象,由平台决定使用libev或IOCP。在node-v0.9.0版本中,libuv移除了libev的内容。

libuv是一个高性能的,事件驱动的I/O库,并且提供了跨平台(如windows, linux)的API。 随着libuv的日益成熟,它成为了拥有卓越性能的系统编程库。除了node.js以外,包括Mozilla的Rust编程语言,和许多的语言都开始使用libuv。 libuv的官网:http://docs.libuv.org/en/v1.x/,英文的,可以直接参考。

(3)V8 concept[概念]

架构图

cmd-markdown-logo

现在 JS 引擎的执行过程大致是:源代码 --->抽象语法树 --->字节码 --->JIT--->本地代码。一段代码的抽象语法树示例如下:

function demo(name) {
    console.log(name);
}
复制代码

V8 更加直接的将抽象语法树通过 JIT 技术转换成本地代码,放弃了在字节码阶段可以进行的一些性能优化,但保证了执行速度。 在 V8 生成本地代码后,也会通过 Profiler 采集一些信息,来优化本地代码。虽然,少了生成字节码这一阶段的性能优化, 但极大减少了转换时间。

JIT就是即时编译器,可以根据字节码的使用频率对常用的字节码生成本地机器指令(运行时),并且保存下来

PS:Tuborfan 将逐步取代 Crankshaft

随着Web相关技术的发展,JavaScript所要承担的工作也越来越多,早就超越了“表单验证”的范畴,这就更需要快速的解析和执行JavaScript脚本。V8引擎就是为解决这一问题而生,在node中也是采用该引擎来解析JavaScript。

Node,开始学习!

1. Node能够解决什么问题?

应用场景:Node的首要目标是提供一种简单的,用于创建高性能服务器的开发工具 Web服务器的瓶颈在于并发的用户量,对比Java和Php的实现方式

Node在处理高并发 , I/O密集场景有明显的性能优势
  • I/O (input/output 文件的输入输出)
  • 高并发,是指在同一时间并发访问服务器
  • I/O密集指的是文件操作、网络操作、数据库,相对的有CPU密集,CPU密集指的是逻辑处理运算、压缩、解压、加密、解密

2. Node是什么?

  • Node.js 是一个基于 Chrome V8引擎的 JavaScript 运行环境,让JavaScript的执行效率与低端的C语言的相近的执行效率。
  • Node.js 使用了一个事件驱动非阻塞式 I/O 的模型,使其轻量又高效。
  • Node.js 的包管理器npm,是全球最大的开源库生态系统。

3. 进程与线程

进程是操作系统分配资源和调度任务的基本单位,线程是建立在进程上的一次程序运行单位,一个进程上可以有多个线程。

为什么JavaScript是单线程?
  • 这是由 Javascript 这门脚本语言的用途决定的。
  • Web Worker并没有改变 JavaScript 单线程的本质。
    // websocket
    let worker  = new Worker('./1.worker.js');
    worker.postMessage(10000);
    worker.onmessage = function (e) {
      console.log(e.data);
    }
    console.log('main thread')
复制代码
3.1 谈谈浏览器

cmd-markdown-logo

  • 用户界面-包括地址栏、前进/后退按钮、书签菜单等
  • 浏览器引擎-在用户界面和呈现引擎之间传送指令(浏览器的主进程)
  • 渲染引擎,也被称为浏览器内核(浏览器渲染进程)
  • 一个插件对应一个进程(第三方插件进程)
  • GPU提高网页浏览的体验(GPU进程)

由此可见浏览器是多进程的,并且从我们的角度来看我们更加关心浏览器渲染引擎

3.2 渲染引擎

渲染引擎内部是多线程的,内部包含两个最为重要的线程ui线程和js线程。这里要特别注意ui线程和js线程是互斥的,因为JS运行结果会影响到ui线程的结果。ui更新会被保存在队列中等到js线程空闲时立即被执行.


3.3 js单线程

javascript在最初设计时设计成了单线程,为什么不是多线程呢?如果多个线程同时操作DOM那岂不会很混乱?这里所谓的单线程指的是主线程是单线程的(node其实也是多线程的),所以在Node中主线程依旧是单线程的。

  • js线程和ui线程 是共享线程
  • 不能两个线程,操作同一个DOM

什么是多线程?
  • 同步
  • 每次请求都会产生一个线程,可能会遇到同时操作一个文件,会给文件加锁
  • 可以开一个工作线程,但是归主线程所管理
  • 需要切换上下文执行
  • 运用线程池可以优化多线程

什么是单线程?
  • 异步
  • 不会占用过多内存,并且不需要在切换执行上下文

什么是进程?
  • 进程是系统分配任务和调度任务的基本单位
  • 多进程 可能会遇到同时操作一个文件会给文件加锁
3.4 其他线程
  • 浏览器事件触发线程(用来控制事件循环,存放setTimeout、浏览器事件、ajax的回调函数)
  • 定时触发器线程(setTimeout定时器所在线程)
  • 异步HTTP请求线程(ajax请求线程)

单线程不需要管锁的问题,这里简单说下锁的概念。例如大家都要去上厕所,厕所就一个,相当于所有人都要访问同一个资源。那么先进去的就要上锁。而对于node来说。就一个人去厕所,所以免除了锁的问题!


3.5. 浏览器中的Event Loop

主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)

cmd-markdown-logo

  1. V8引擎解析JavaScript脚本。
  2. 解析后的代码,调用Node API。
  3. libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
  4. V8引擎再将结果返回给用户。
queue stack
  • queue队列: 先进先出 push shift
  • stack栈: 函数执行

3.6 Node.js的Event Loop

cmd-markdown-logo


3.7. 同步与异步

同步和异步关注的是消息通知机制

  • 同步就是发出调用后,没有得到结果之前,该调用不返回,一旦调用返回,就得到返回值了。 简而言之就是调用者主动等待这个调用的结果。
  • 而异步则相反,调用者在发出调用后这个调用就直接返回了,所以没有返回结果。换句话说当一个异步过程调用发出后,调用者不会立刻得到结果,而是调用发出后,被调用者通过状态、通知或回调函数处理这个调用。

补充知识:宏任务 && 微任务(vue $nextTick) 异步方法

// 宏任务 setTimeout
// 同步代码执行后才会执行异步
// 根据时间排序,当时间到达后把对应的回调放到队列
    setTimeout(() => {
      console.log(1);
      setTimeout(() => {
        console.log(4);
      }, 1000);
    }, 1000);
    setTimeout(() => {
      console.log(2);
    }, 2000);
    setTimeout(() => {
      console.log(3);
    }, 3000);
复制代码
    // 宏任务 setImmedate
    // setImmedate只兼容ie,默认是低于 setTimeout
    setImmediate(function () {
      console.log('setImmediate')
    })
    setTimeout(function () {
      console.log('timeout')
    }, 4);
    console.log(1);
复制代码
    // 宏任务 MessageChannel
    let messageChannel = new MessageChannel();
    let prot2 = messageChannel.port2;
    // postMessage是异步执行的,要等待同步都执行完后才会被调用
    messageChannel.port1.postMessage('111');
    console.log(1);
    prot2.onmessage = function (e) {
      console.log(e.data);
    }
    console.log(2);
复制代码
     // 宏任务 MutationObserver废弃了,兼容/性能有问题
     let observe = new MutationObserver(function () {
        alert('已经dom更新好了')
     });
     observe.observe(app,{childList:true});
     for(let i = 0;i<1000;i++){
        app.appendChild(document.createElement('span'));
    }
复制代码

3.8. 阻塞与非阻塞I/O

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

3.9. 组合

同步异步取决于被调用者,阻塞非阻塞取决于调用者

  • 同步阻塞
  • 异步阻塞
  • 同步非阻塞
  • 异步非阻塞

4. 什么场合下应该考虑使用Node框架?

当应用程序需要处理大量并发的输入输出,而在向客户端响应之前,应用程序并不需要进行非常复杂的处理。

  • 聊天服务器
  • 电子商务网站

原文发布时间:2018年07月02日
原文作者:张倩1488543336000
本文来源 掘金,如需转载请紧急联系作者

相关文章
|
7月前
|
JavaScript
Turndown 源码分析:五、节点相关`root-node.js`和`node.js`
Turndown 源码分析:五、节点相关`root-node.js`和`node.js`
75 0
|
JSON 缓存 JavaScript
Node.js躬行记(20)——KOA源码分析(下)
Node.js躬行记(20)——KOA源码分析(下)
|
缓存 JSON JavaScript
Node.js躬行记(19)——KOA源码分析(上)
Node.js躬行记(19)——KOA源码分析(上)
|
网络性能优化 索引 调度
5. VPP源码分析(graph node(3))
2.3. vlib_frame_t 从arguments地址开始的内存空间是vector空间,作为结点接收数据包索引的缓冲区。 2.3.1. vlib_frame_t内存空间 每个线程都会从堆中分配(mmap)一块内存clib_per_cpu_mheapsvm->heap_base = clib_.
6875 0
|
网络性能优化 调度 索引
4. VPP源码分析(graph node(2))
2.2.2. VLIB_NODE_TYPE_PROCESS结点 每个process结点是由jump机制构成的一个协程,协程主要用于等待、处理事件。使用longjmp/setjmp的轻量级多任务协程,由应用进程自行进行调度,不受操作系统调度机制的影响,上下文切换只损耗调用longjmp/setjmp的时间。
7910 1
|
网络性能优化 调度 索引
3. VPP源码分析(graph node(1))
2.1. 与结点相关的结构体 2.1.1. 全局结构体 vlib_main_t:每个线程一份,记录着线程使用到的全局数据信息.比如: /* Node graph main structure.
9775 1
|
网络协议 Unix
源码分析Node的Cluster模块
### 从源码分析Node的Cluster模块 前段时间,公司的洋彬哥老哥遇到一个问题,大概就是本机有个node的http服务器,但是每次请求这个服务器的端口返回的数据都报错,一看返回的数据根本不是http的报文格式,然后经过一番排查发现是另外一个服务器同时监听了http服务器的这个端口。这个时候洋彬老哥就很奇怪,为啥我这个端口明明使用了,却还是可以启动呢?这个时候我根据以前看libuv源码
4718 0
|
1月前
|
Web App开发 JavaScript 前端开发
2024年5月node.js安装(winmac系统)保姆级教程
本篇博客为2024年5月版Node.js安装教程,适用于Windows和Mac系统。作者是一名熟悉JavaScript与Vue的大一学生,分享了Node.js的基本介绍、下载链接及简单安装步骤。安装完成后,通过终端命令`node -v`验证版本即可确认安装成功。欢迎关注作者,获取更多技术文章。
28 2
2024年5月node.js安装(winmac系统)保姆级教程
|
29天前
|
存储 JavaScript 搜索推荐
Node框架的安装和配置方法
安装 Node 框架是进行 Node 开发的第一步,通过正确的安装和配置,可以为后续的开发工作提供良好的基础。在安装过程中,需要仔细阅读相关文档和提示,遇到问题及时解决,以确保安装顺利完成。
83 2
下一篇
DataWorks