1 Node.js概述

1.1 Node.js不是什么

  • 不是web框架(Flask,Spring)
  • 不是编程语言(Python,PHP)

1.2 Node.js是什么

  • 是一个平台
    • 多种技术组合起来
    • JavaScript也能调用系统接口,开发后端应用
  • Node.js搭建技术
    • V8引擎
    • libuv
    • C/C++实现的c-ares(解析dns域名)、http-parser(解析http)、OpenSSL(加密解密)、zlib(数据压缩)等库

2 Node.js技术架构

image-20210730103247379

https://github.com/yjhjstz/deep-into-node

2.1 Node.js bindings

  • 背景

    • Node.js用C++对http_parser进行封装,使它符合某些要求(统一数据类型等),封装的文件叫做http_parser_bindings.cpp
    • 用Node.js提供的编译工具将其编译为.node文件
    • JS代码可以直接require这个.node文件
    • 这样JS就能调用C++库,中间的桥梁就是binding
    • 由于Node.js提供了很多binding,所以加个s
    • 这就是bindings
  • 概念

    • Bindings层主要是一些胶水代码
    • 在Node.js中,Bindings层所做的就是把底层的C/C++接口暴露给JavaScript环境,从而打通JavaScript与C/C++之间的相互调用
  • JS与C++交互

    http://nodejs.cn/api/addons.html#addons_function_arguments

  • C++调用JS回调

    http://nodejs.cn/api/addons.html#addons_callbacks

2.2 libuv

  • 背景

    • 每个系统上的异步I/O(系统与外界(硬盘文件、打印机、网络请求)交互)都不一样
    • 比如FreeBSD系统上有kqueue、Linux上有epoll、Windows上有IOCP
    • Ryan为了一个跨平台的异步I/O库,开始写libuv
    • libuv会根据系统自动选择合适的异步方案
  • 功能

    • 可以用于TCP(建立HTTP服务器)/UDP(qq聊天)/DNS(网址对应的ip)/文件(读写)等的异步操作
    • 在运行时负责一个事件循环(Event Loop)、一个线程池、文件系统I/O、DNS相关的I/Oy以及网络I/O等。

2.3 V8

  • 功能
    • 将JS源代码变成**本地代码(机器代码)**并执行
    • 维护调用栈,确保JS函数的执行顺序
    • 内存管理,为所有对象分配内存
    • 垃圾回收,重复利用无用的内存
    • 实现JS的标准库(数组的方法等)
  • 注意
    • V8不提供DOM API(浏览器提供)
    • V8执行JS是单线程
    • 可以开启两个线程分别执行JS
    • V8本身是包含多个线程的,如垃圾回收为单独线程
    • 自带event loop,但Node.js基于libuv自己做了一个

2.4 Event Loop

  • 什么是Event

    • 事件分为内部与外部
    • 计时器到期了(内部事件)
    • 文件可以读取了、读取出错了(外部事件)
    • socket(HTTP消息)有内容了,关闭了(外部事件)
  • 什么是Loop

    • loop就是循环,由于事件是分优先级的,所以处理起来也是分先后的
    • 所以Node.js需要按顺序轮询每种事件
    • 这种轮询往往都是循环的,1–>2–>3–>1–>2–>3
  • Event Loop

    • 操作系统可以触发事件,JS可以处理事件
    • Event Loop就是对事件处理顺序的管理
  • 顺序示意图

image-20210730110930457

https://juejin.cn/post/6844903582538399752

  • 重点阶段
    • **timers**检查计时器
    • poll轮询,检查系统事件
    • **check**检查setImmediate回调
    • 其他阶段用的较少
  • 注意
    • 大部分时间,Node.js都停在poll轮询阶段
    • 大部分事件都在poll阶段被处理,如文件、网络请求
    • poll阶段是有停留时间限制的,不同系统停留时间不同
  • setTimeout(f1, 0)和setImmediate(f2)哪个先执行?
    • 由于大部分时间js都停留在poll阶段,之后进入check阶段,从而执行setImmediate;之后进入timers阶段,才会执行setTimeout
    • 只有当Node.js最开始运行时,会首先进入timers阶段执行setTimeout
  • 小结
    • Node.js的js线程是单线程运行的,通过一个事件循环来循环取出消息队列中的消息进行处理,处理过程基本上就是去调用该消息的回调函数。
    • 事件驱动程序设计(Event-Driven Programming)模型是Node.js的重要特性之一。这种模型的程序运行流程是由用户的操作(如鼠标的按键、键盘的按键操作)或者是由其他程序的消息来驱动的。

2.5 总结

  • libuv进行异步I/O操作
  • 基于libuv,Node.js用Event Loop管理事件处理顺序
  • C/C++库高效处理DNS/HTTP
  • bindings让JS能和C/C++沟通(require)
  • V8运行JS
  • Node.js标准库简化JS代码

2.6 Node.js工作流程

image-20210730131938322

  • Node.js应用启动时,会开启JS线程(主线程)、由libuv提供的线程池和一个事件循环。JS线程负责执行应用代码,当发现有I/O操作时,直接提交给libuv的线程池并注册回调函数不会等待I/O结束后再继续运行,而是拿到一个状态后继续执行,这就是“单线程非阻塞I/O

  • I/O操作结束之后会有一个事件,该事件会放在事件队列中,事件循环每次都会检查是否有事件需要处理,如果有就处理,否则进行下一轮轮询;如果没有任何事件要处理则退出进程。这就是“事件驱动

  • 在I/O密集型应用中,主线程只负责提交任务,轮询结果,耗时的任务执行部分会提交给底层执行,这就是Node.js为什么会有如此高性能的原因。

2.7 Node.js API

image-20210730133449418

  • Node standard library
    • 比较薄的API封装层
    • 提供网络、文件、事件等操作,但实际是由底层来完成的

3 Node.js优缺点

3.1 优点

  • 事件驱动、异步编程,在I/O密集场景下有着极高的性能
  • 轻量高效,资源占用率低
  • 使用JS作为应用层语言,语言门槛低

3.2 缺点

  • 单进程,一旦JS线程出现未处理的错误,进程会退出,服务会终止
  • 单线程(特指JS线程),一旦JS线程上出现耗时的CPU计算(加解密之类的操作),JS线程将出现阻塞,会拖慢事件轮询。

4 Node.js适用场景

  1. Restful API
  2. 实时WebSocket应用
  3. 前端工具链
  4. 桌面开发