跳至主要內容

nodeJS

晨光-向大约 9 分钟

Node.js

文章来源:Node.js简介 - 知乎 (zhihu.com)open in new window

Node.js 介绍

Node.js 是一种开源与跨平台的 JavaScript 的运行环境,能够使得javascript脱离浏览器运行。 它是一个可用于几乎任何项目的流行工具,允许我们通过JavaScript和一系列模块来编写服务器端应用和网络相关的应用。

核心模块包括文件系统I/O、网络(HTTP、TCP、UDP、DNS、TLS/SSL等)、二进制数据流、加密算法、数据流等等。Node模块的API形式简单,降低了编程的复杂度。

使用框架可以加速开发。常用的框架有Express.js、http://Socket.IOopen in new window和Connect等。Node.js的程序可以在Microsoft Windows、Linux、Unix、Mac OS X等服务器上运行。Node.js也可以使用TypeScript(强化了数据类型的JavaScript变体)、Dart语言,以及其他能够编译成JavaScript的语言编程。

Node.js主要用于编写像Web服务器一样的网络应用,这和PHP和Python是类似的。但是Node.js与其他语言最大的不同之处在于,PHP等语言是阻塞的(只有前一条命令执行完毕才会执行后面的命令),而Node.js是非阻塞的(多条命令可以同时被运行,通过回调函数得知命令已结束运行)。

Node.js是事件驱动的。开发者可以在不使用线程的情况下开发出一个能够承载高并发的服务器。其他服务器端语言难以开发高并发应用,而且即使开发出来,性能也不尽如人意。Node.js正是在这个前提下被创造出来。Node.js把JavaScript的易学易用和Unix网络编程的强大结合到了一起。

Node.js 历史

Node.js于2009年写成,原始作者是瑞安·达尔( Ryan Dahl)。Node.js结合了Google的V8、事件驱动模式和低级I/O接口,其设计灵感源自Flickr的一款上传进度栏:在上传过程中,浏览器并不清楚有多少文件已经发送到服务器,除非向服务器进行查询,于是达尔想出了一个更简便的方法。Node.js的开发和维护工作由达尔本人主持,而他所在的Joyent公司也提供了赞助。2009年11月8日,达尔在欧洲JSConf大会上展示了Node.js项目,并受到了观众赞赏。在演讲中,达尔针对Apache HTTP Server和顺序编程方式提出了批评,认为Apache处理大量并发连接(10,000甚至更多)的可能性有限,而且顺序编程方式在多连接情况下会造成阻塞,或者消耗更多资源;而Node.js提供了基于事件驱动和非阻塞的接口,可用于编写高并发状态下的程序,而且JavaScript的匿名函数、闭包、回调函数等特性就是为事件驱动而设计的。

2010年1月,一款名为“npm”的软件包管理系统诞生。npm使程序员能够更方便地发布和分享Node.js类库及源代码,而且简化了类库安装、升级与卸载的过程。Node.js最初只支持Linux和Mac OS X操作系统。2011年6月,微软和Joyent共同合作,把Node.js移植到了Windows系统上面,并且在7月发布了第一个正式支持Windows系统的版本。

2012年1月,达尔离开了Node.js项目,开发工作由他的同事以及npm创始人艾萨克·施吕特(Isaac Schlueter)继续主持。2014年2月,蒂莫西·费里斯(Timothy J. Fontaine)接任项目主管。

由于长期对Joyent的管理感到不满,Node.js核心开发者Fedor Indutny在2014年12月制作了分支版本,并起名“io.js”。与Node.js相对的是,io.js采用开放管理模式进行管理,并计划始终采用最新版的V8引擎。为了在用户、厂商和开发者之间获取平衡,Node.js基金会于2015年初成立。基金会得到了IBM、Intel、微软、Joyent等公司的支持。6月,Node.js和io.js开发者社区共同决定合并到Node.js基金会之下。同年9月,Node 4.0发布,Node.js和io.js正式合并。4.0版引入了ES6的语言特性和“长期支持版本”的发布周期。到了2016年,io.js宣布不再发布新版本,并建议开发者换回Node.js。

Node.js 技术架构

img
img

1. bindings 和 C/C++插件

在核心模块之下,有一层 C++ Bindings,将上层的 JavaScript 代码与下层 C/C++类库桥接起来。

底层模块为了更好的性能,采用 C/C++实现,而上层的 JavaScript 代码无法直接与 C/C++通信,因而需要一个桥梁(即 Binding)。另一方面,通过 Bindings 也可以复用可靠的老牌开源类库,而不必手搓所有底层模块。

Node.js 插件是用 C++ 编写的动态链接共享对象,可以使用 require() 函数加载到 Node.js 中,且像普通的 Node.js 模块一样被使用。 它们主要用于为运行在 Node.js 中的 JavaScript 与 C/C++ 库之间提供接口。

2. libuv

Libuv是一个高性能的,事件驱动的异步I/O库,它本身是由C语言编写的,具有很高的可移植性。libuv封装了不同平台底层对于异步IO模型的实现,所以它还本身具备着Windows, Linux都可使用的跨平台能力。

Libuv专为Node.js而设计,但是后来因为它这种事件驱动的异步IO的高效模型逐步被很多语言和项目都采纳而作为自身的底层库而使用,像 Luvit, Julia, pyuv, 还有很多基于它的项目。

Nodejs刚出来的时候,底层并不是使用libuv,而是libev,libev本身也是一个异步IO的库,但是它只能在POSIX系统下使用。随着nodejs被越来越多人使用,由于windows的用户量巨大,所以开始考虑Nodejs的跨平台能力。

这时候Nodejs提供了libuv来作为抽象封装层,在Unix系统上,通过封装libev和libio调用linux的epoll 或 kqueue,在Windows 平台上的IOCP进行封装,自此之后Nodejs具备了跨平台能力,由Libuv作为中间层本身提供的跨平台的抽象,来根据系统决定使用libev/libio或IOCP,后来在node-v0.9.0版本中,libuv移除了libev的内容。

3、V8

Node.js使用Google V8 JavaScript引擎,因为:

  • V8是基于BSD许可证的开源软件
  • V8速度非常快
  • V8专注于网络功能,在HTTP、DNS、TCP等方面更加成熟

V8是为Google Chrome设计的JavaScript运行引擎,Google于2008年将其开源。V8用C++写成,在运行之前将JavaScript编译成了机器代码,而非字节码或是解释执行它,以此提升性能。更进一步,使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript程序与V8引擎的速度媲美二进制编译。

Node.js用libuv来处理异步事件,而V8提供了JavaScript的实时运行环境。

4、事件循环(Event Loop)

Node.js将其注册到操作系统中,这样可以及时注意到新连接的产生。当新连接产生时,操作系统会产生一个回调。在Node.js运行时内部,每个连接都被分配一个小型的堆。与其他服务器程序不同的是,Node.js不使用进程或线程处理连接,而是采用事件循环来处理并发连接。而且Node.js的事件循环不需要手动调用。在回调函数定义之后,服务器进入事件循环。当回调函数均被执行完毕之后,Node.js结束事件循环。

事件循环是 Node.js 处理非阻塞 I/O 操作的机制。尽管 JavaScript 是单线程处理的,当有可能的时候,它们会把操作转移到系统内核中去;目前大多数内核都是多线程的,它们可在后台处理多种操作。当其中的一个操作完成的时候,内核通知 Node.js 将适合的回调函数添加到轮询队列中等待时机执行。

当 Node.js 启动后,它会初始化事件循环,处理已提供的输入脚本,它可能会调用一些异步的 API、调度定时器,或者调用 process.nextTick(),然后开始处理事件循环。

下面的图表展示了事件循环操作顺序的简化概览。

img
img

每个阶段都有一个 FIFO 队列来执行回调。虽然每个阶段都是特殊的,但通常情况下,当事件循环进入给定的阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中的回调,直到队列用尽或最大回调数已执行。当该队列已用尽或达到回调限制,事件循环将移动到下一阶段。

由于这些操作中的任何一个都可能调度更多的操作和由内核排列在轮询阶段被处理的新事件, 且在处理轮询中的事件时,轮询事件可以排队。因此,长时间运行的回调可以允许轮询阶段运行长于计时器的阈值时间。

各阶段概览

  • timers (定时器):这个阶段执行 setTimeout 和 setInterval 的回调函数。
  • I/O callbacks (待定回调):不在 timers 阶段、close callbacks 阶段和 check 阶段这三个阶段执行的回调,都由此阶段负责,这几乎包含了所有回调函数。
  • idle, prepare :event loop 内部使用的阶段。
  • poll (轮询):检索新的 I/O 事件;执行与 I/O 相关的回调。在某些场景下 Node.js 会阻塞在这个阶段。
  • check(检测):执行 setImmediate() 的回调函数。
  • close callbacks (关闭的回调函数):执行关闭事件的回调函数,如 socket.on('close', fn) 里的 fn。

5、API

Node.js将浏览器、数据(例如MongoDB或CouchDB)等组合到一起,通过JSON提供一个统一的接口。由于前端框架和一些基本的后端开发技术(如MVC、MVP、MVVM等)变得流行,Node.js也支持客户端和服务器端重新利用相同的模型和接口。

包管理器

NPM

YARN

参考文档:yarn

PNPM

参考文档:pnpm