乐趣区

关于node.js:带你重新认识Node

写作不易,未经作者容许禁止以任何模式转载!
如果感觉文章不错,欢送关注、点赞和分享!
继续分享技术博文,关注微信公众号 👉🏻 前端 LeBron
原文链接

最后做 Node 的目标是什么?

Node 作者 Ryan Dahl:

基于 V8 创立一个轻量级的高性能 Web 服务器并提供一套库

为什么是 JavaScript?

Ryan Dahl 是一名资深的 C /C++ 程序员,发明出 Node 之前次要工作是围绕 Web 高性能服务器进行的

他发现 Web 高性能服务器的两个要点:

  • 事件驱动
  • 非阻塞 I / O

Ryan Dahl 也曾评估过应用 C、Lua、Haskell、Ruby 等语言作为备选实现,得出以下论断:

  • C 的开发门槛高,能够预感不会有太多的开发者能将它用于业务开发
  • Ryan Dahl 感觉本人还不足够玩转 Haskell,所以舍弃它
  • Lua 本身曾经含有很多阻塞 I / O 库,为其构建非阻塞 I / O 库不能扭转开发者应用习惯
  • Ruby 的虚拟机性能不佳

JavaScript 的劣势:

  • 开发门槛低
  • 在后端畛域没有历史包袱
  • 第二次浏览器大战慢慢分出高下,Chrome 浏览器的 JavaScript 引擎 V8 摘得性能第一的桂冠

Node 给 JavaScript 带来的意义

除了 HTML、Webkit 和显卡这些 UI 相干技术没有反对外,Node 的构造与 Chrome 十分相似。他们都是基于事件驱动的异步架构:

  • 浏览器通过事件驱动来服务界面上的交互
  • Node 通过事件驱动来服务 I / O

在 Node 中,JavaScript 还被赋予了新的能力:

  • 得心应手地拜访本地文件
  • 搭建 WebSocket 服务端
  • 连贯数据库,进行业务研发
  • 像 Web Worker 一样玩转多过程

Node 使 JavaScript 能够运行在不同的中央,不再限度在浏览器中、DOM 树打交道。如果 HTTP 协定是水平面,Node 就是浏览器在协定栈另一边的倒影。

Node 不解决 UI,但用与浏览器雷同的机制和原理运行,突破了 JavaScript 只能在浏览器中运行的场面。前后端编程环境对立,能够大大降低前后端转换所须要的上下文代价。

Node 的特点

异步 I / O

  • 以读取文件为例

var fs = require('fs'); 

fs.readFile('/path', function (err, file) {console.log('读取文件实现') 
}); 

console.log('发动读取文件'); 

相熟的用户必晓得,“读取文件实现”是在“发动读取文件”之后输入的

fs.readFile 后的代码是被立刻执行的,而“读取文件实现”的执行工夫是不被预期的

只晓得它将在这个异步操作后执行,但并不知道具体的工夫点

异步调用中对于后果值的捕捉是合乎“Don’t call me, I will call you”准则的

这也是重视后果,不关怀过程的一种体现

Node 中,绝大多数操作都以异步的形式进行调用,Ryan Dahl 排除万难,在底层构建了很多异步 I / O 的 API,从文件读取到网络申请等。使开发者很已从语言层面很天然地进行并行 I / O 操作,在每个调用之间无需期待之前的 I / O 调用完结,在编程模型上能够极大晋升效率

注:异步 I / O 机制将在下文中具体论述

事件与回调函数

事件

随着 Web2.0 的到来,JavaScript 在前端负责了更多的职责,工夫也失去了宽泛的利用。将前端浏览器中广泛应用且成熟的事件与回到函数引入后端,配合异步 I / O,能够很好地将事件产生的工夫点裸露给业务逻辑。

  • 服务端例子

对于服务器绑定了 request 事件

对于申请对象,绑定了 data 和 end 事件

var http = require('http'); 
var querystring = require('querystring'); 

// 侦听服务器的 request 事件 
http.createServer(function (req, res) { 
 var postData = ''; 
 req.setEncoding('utf8'); 
 // 侦听申请的 data 事件 
 req.on('data', function (trunk) {postData += trunk;});

 // 侦听申请的 end 事件
 req.on('end', function () {res.end(postData); 
 }); 
}).listen(8080); 

console.log('服务器启动实现'); 
  • 前端例子

发出请求后,只需关怀申请胜利时执行相应的业务逻辑即可

request({ 
 url: '/url', 
 method: 'POST', 
 data: {}, 
 success: function (data) {// success 事件} 
}); 

事件的编程形式具备轻量级、松耦合、只关注事务点等劣势,然而在多个异步工作的场景下,事件与事件之间各自独立,如何合作是一个问题,后续也呈现了一系列异步编程解决方案:

  • 事件公布 / 订阅模式
  • Promise、async / await
  • 流程管制库

回调函数

  • Node 除了异步和事件外,回调函数也是一大特色
  • 纵观下来,回调函数也是最好的接管异步调用返回数据的形式

    • 然而这种编程形式对于很多习惯同步思路编程的人来说,兴许是非常不习惯的
    • 代码的编写程序与执行程序并无关系,这对他们可能造成浏览上的阻碍
  • 在流程管制方面,因为交叉了异步办法和回调函数,与惯例的同步形式相比变得不那么高深莫测了

    • 转变为异步编程思维后,通过对业务的划分和对事件的提炼,在流程管制方面解决业务的复杂度是与同步形式实际上是统一的

单线程

Node 放弃了 JavaScript 在浏览器中单线程的特点

JavaScript 与其余线程是无奈共享任何状态的,最大的益处是不必像多线程编程那样处处在意状态的同步问题,这里没有死锁的存在,也没有线程上下文交换所带来的性能上的开销

  • 单线程的毛病

    • 无奈利用多核 CPU
    • 谬误会引起整个利用退出,健壮性较差
    • 大量计算占用 CPU 导致无奈持续调用异步 I / O
  • 后续也推出了 child_process 和 cluster 模块较好地缓解了以上毛病

跨平台

起初 Node 只能在 Linux 平台上运行,如果想在 Windows 平台上学习和应用 Node,则必须通过 Cygwin / MinGW,后微软投入通过基于 libuv 实现跨平台架构

  • libuv

在操作系统与 Node 下层模块零碎之间构建了一层平台架构

通过良好的架构,Node 的第三方 C ++ 模块也能够借助 libuv 实现跨平台

Node 模块机制 – CommonJS

背景:

在其余高级语言中,Java 有类文件,Python 有 import 机制,Ruby 有 require,PHP 有 include 和 require。而 JavaScript 通过 script 标签引入代码的形式显得横七竖八,。人们不得不用命名空间等形式人为地束缚代码,以达到平安和易用的目标。

直到起初呈现了 CommonJS…

愿景

心愿 JavaScript 能欧在任何中央运行

出发点

对于 JavaScript 本身而言,它的标准仍然是单薄的,还有以下缺点:

  • 没有模块零碎
  • 规范库较少

    • ECMAScript 仅定义了局部外围库
    • 对于文件系统 I / O 流等常见需要没有规范 API
  • 没有标准接口

    • 在 JavaScript 中,简直没有定义过如 Web 服务器或者数据库之类的规范对立接口
  • 不足包管理系统

    • 导致 JavaScript 利用中根本没有主动加载和装置以来的能力

CommonJS 的提出,次要是为了补救以后 JavaScript 没有规范的缺点,以达到像 Python、Ruby 和 Java 具备开发大型利用的根底能力,而不是停留在小脚本程序的阶段,心愿能够利用 JavaScript 开发:

  • 服务端 JavaScript 程序
  • 命令行工具
  • 桌面图形界面应用程序
  • 混合利用

CommonJS 标准涵盖了:

  • 模块
  • 二进制
  • Buffer
  • 字符集编码
  • I / O 流
  • 过程环境
  • 文件系统
  • 套接字
  • 单元测试
  • Web 服务器网关接口
  • 包治理

Node 与浏览器以及 W3C 组织、CommonJS 组织、ECMAScript 之间的关系,独特形成了一个凋敝的生态系统

模块标准

  • 模块定义

上下文提供了 exports 对象用于导出以后模块的办法或者变量,并且它是导出的惟一进口

在模块中,还存在一个 module 对象,它代表模块本身,而 exports 是 module 的属性

在 Node 中,一个文件就是一个模块,将办法挂载在 exports 对象上作为属性即可定义导出的形式

// math.js
exports.add = function(a, b){return a + b;}
  • 模块援用
const math = require('./math');

const res = math.add(1, 1);
console.log(res);
// 2

在 CommonJS 标准中,存在 require 办法,这个办法承受模块标识,以此引入一个模块的 API 到以后上下文中

  • 模块标识

模块标识就是传递给 require 办法的参数,能够是:

  • 如何小驼峰命名的字符串
  • 以./、../ 结尾的相对路径 or 绝对路径
  • 能够没有文件名后缀.js

模块的定义非常简略,接口也非常简洁

每个模块具备独立的空间,它们互不烦扰,在援用时也显得干净利落

  • 意义:

将类聚的办法和变量等限定在公有的作用域中,同时反对引入和导出性能以顺畅地连贯上下游依赖

模块实现

在 Node 引入模块,须要经验以下三个步骤

  • 路径分析
  • 文件定位
  • 编译执行

Node 中模块分为两类:

  • 外围模块

编译过程中,编译进了二进制执行文件

在 Node 过程启动时,局部外围模块就间接被加载进内存中,所以这部分外围模块引入时,文件定位和编译执行这两个步骤能够省略,并且在路径分析中优先判断,所以它的加载速度是最快的。

  • 用户编写的文件模块

运行时动静加载,须要残缺的路径分析、文件定位、编译执行过程,速度比外围模块慢

优先从缓存加载

与浏览器会缓存动态脚本文件以进步性能一样,Node 对引入过的模块都会进行二次缓存,以缩小二次引入时的开销。不同点在于:

  • 浏览器仅缓存文件
  • Node 缓存的是编译和执行之后的对象

无论外围模块还是文件模块,require 办法对雷同模块的二次加载都一律采纳缓存优先的形式

路径分析和文件定位

标识符剖析(门路)

后面说到过,require 办法承受一个参数作为标识符,分为以下几类:

  • 外围模块

优先级仅次于缓存加载,在 Node 的源代码编译过程中已编译为二进制代码,加载过程最快

注:加载一个与外围模块标识符雷同的自定义模块是不会胜利的,只能通过抉择不同的标识符 / 换用门路的形式实现

  • 门路模式的文件模块

以 ./、../ 结尾的标识符都被当做文件模块解决

require 办法会将门路转为实在门路,并以实在门路为索引,将编译执行后的后果寄存到缓存中,以使二次加载更快

文件模块给 Node 指明了确切的文件地位,所以在查找过程中能够节约大量工夫,其加载速度仅慢于外围模块

  • 自定义模块

是一种非凡的文件模块,是一个文件或者包的模式

这类模块的查找是最费时的,也是最慢的一种

先介绍一下模块门路这个概念,也是定位文件模块时制订的查找策略,具体表现为一个门路组成的数组

  • console.log(module.path)
  • 你能够失去一个门路数组

[‘/home/bytedance/reasearch/node_modules’,

‘/home/bytedance/node_modules’,

‘home/node_module’, /node_modules’]

能够看出规定如下:

  • 以后文件目录下的 node_modules 目录
  • 父目录下的 node_modules 目录
  • 父目录的父目录下的 node_modules 目录
  • 沿路径向上逐级递归,直到根目录下的 node_modules 目录

它的生成形式与 JavaScript 原型链 / 作用域链的查找形式非常相似

在加载过程中,Node 会一一尝试模块门路中的门路,直到找到指标文件

文件门路越深,模块查找耗时会越多,这是自定义模块的加载速度最慢的起因

文件定位

  • 文件扩展名剖析

require 剖析标识符会呈现不蕴含文件扩展名的状况

会按.js、.json、.node 的秩序补足扩展名,一次尝试

过程中,需调用 fs 模块同步阻塞地判断文件是否存在,Node 单线程因而会引起性能问题

如果是.node / .json 文件带上扩展名能放慢点速度,配合缓存机制,可大幅缓解 Node 单线程阻塞调用的缺点

  • 目录剖析和包

剖析标识符的过程中可能没有找到文件,却失去一个目录,则会将目录当做一个包来解决

通过解析 package.json 文件对应该包的 main 属性指定的文件名

如果 main 相应文件解析谬误 / 没有 package.json 文件,node 会将 index 作为文件名

一次查找 index.js index.json index.node

该目录没有定位胜利则进行下一个模块门路进行查找

直到模块门路数组都被遍历完仍然没有查找到指标文件则抛出异样

模块编译

在 Node 中,每个文件模块都是一个对象

function Module(id, parent) { 
 this.id = id; 
 this.exports = {}; 
 this.parent = parent; 
 if (parent && parent.children) {parent.children.push(this); 
 } 
 this.filename = null; 
 this.loaded = false; 
 this.children = [];} 
  • js 文件

    • 通过 fs 模块同步读取文件后编译执行
  • node 文件

    • 这是用 C /C++ 编写的扩大文件,通过 dlopen 办法加在最初编译生成的文件
  • json 文件

    • 通过 fs 模块同步读取文件后,JSON.parse 解析返回的后果
  • 其余

    • 都被当作 js 文件载入

每一个编译胜利的模块都会将其文件门路作为索引存在 Module.cache 对象上,以进步二次引入的性能

包与 NPM

Node 组织了本身外围模块,也使得第三方文件模块能够有序地编写和应用

然而在第三方模块中,模块与模块之间依然是散列在各地的,相互之间不能间接援用

而在模块之外,包和 NPM 则是将模块分割起来的一种机制

肯定水平上解决了变量依赖、依赖关系等代码组织性问题

包构造

包实际上是一个存档文件,即一个目录间接打包为一个.zip/tar.gz 格局的文件,装置后解压还原为目录

  • 合乎 CommonJS 标准的包目录应该蕴含如下文件

    • package.json 包形容文件
    • bin 用于寄存可执行二进制文件
    • lib 用于寄存 JavaScript 代码的目录
    • doc 用于寄存文档的目录
    • test 用于寄存单元测试用例的代码

包形容文件

package.json

CommonJS 为 package.json 定义了如下一些必须的字段

  • name 包名
  • description 包简介
  • version 版本号
  • keywords 关键词数组,用于做 npm 搜寻
  • maintainers 包维护者列表
  • contributors 贡献者列表
  • bugs 一个能够反馈 bug 的网页地址 / 邮件地址
  • licenses 许可证列表
  • repositories 托管源代码的地位列表
  • dependencies 应用以后包所须要依赖的包
  • homepage 以后包的网站地址
  • os 操作系统反对列表

    • aix、freebsd、linux、macos、solaris、vxworks、windows
  • cpu CPU 架构的反对列表

    • arm、mips、ppc、sparc、x86、x86_64
  • builtin 标记以后包是否是内建在底层零碎的规范组件
  • implements 实现标准的列表
  • scripts 脚本阐明对象

包标准的定义能够帮忙 Node 解决依赖包装置的问题,而 NPM 正是基于该标准进行了实现

NPM 罕用性能

CommonJS 包标准是实践,NPM 是其中一种实际

NPM 于 Node,相当于 gem 于 Ruby,pear 于 PHP

帮忙实现了第三方模块的公布、装置和依赖等

  1. 查看帮忙
  • 查看版本 npm -v
  • 查看命令 npm
  1. 装置依赖包
npm install {packageName}

执行该命令后,NPM 会在当前目录下创立 node_modules 目录下创立包目录,接着将相应的包解压到这个目录下

  • 全局装置模式
npm install {packageName} -g

全局模式并不是将一个模块包装置为一个全局包的意思,它并不意味着能够从任何中央 reuqire 它

全局模式这个成为并不准确,-g 实际上是将一个包装置为全局可用的执行命令

它依据包形容文件中的 bin 字段配置,将理论脚本链接到与 Node 可执行文件雷同的门路下

  • 从本地装置

对于一些没有公布到 NPM 上的包,或者因为网络起因无奈间接装置的包

能够通过将包下按在到本地,而后本地装置

npm install <tarball file>
npm install <tarball url>
npm install folder>
  • 从非官方源装置

如果不能通过官网源装置,能够通过镜像源装置

npm install --registry={urlResource}

如果应用过程中简直全应用镜像源,能够指定默认源

npm config set registry {urlResource}
  1. NPM 钩子命令

package.json 中 scripts 字段的提出就是让包在装置或者卸载等过程中提供钩子机制

"scripts":{
    "preinstall": "preinstall.js",
    "install": "install.js",
    "uninstall": "uninstall.js",
    "test": "test.js",
}
  • Install

    • 在以上字段执行 npm install <package> 时,preinstall 指向的脚本会被加载执行,而后 install 指向的脚本会被执行
  • Uninstall

    • 执行 npm uninstall <package> 时,uninstall 指向的脚本兴许会做一些清理工作
  • Test

    • 执行 npm test 将会运行 test 指向的脚本,一个优良的包该当蕴含测试用例,并在 package.json 文件正配置好运行测试的命令,不便用户运行测试用例,以便测验包是否稳固牢靠

局域 NPM

  • 背景

企业的限度在于,一方面须要享受到模块开发带来的低耦合和我的项目组织上的益处,另一方面却要思考模块保密性的问题。所以,通过 NPM 共享和公布存在潜在的危险。

  • 解决方案

为了同时可能享受到 NPM 上泛滥的包,同时对本人的包进行窃密和限度,现有的解决方案就是企业搭建本人的 NPM 仓库,NPM 无论是它的服务端和客户端都是开源的。

局域 NPM 仓库的搭建办法与搭建镜像站的形式简直一样,与镜像仓库不同的中央在于能够抉择不同步官网源仓库中的包

  • 作用

    • 公有的可重用模块能够打包到局域 NPM 仓库中,这样能够放弃更新的中心化,不至于让各个小我的项目保护雷同性能的模块
    • 杜绝通过复制粘贴实现代码共享的行为

异步 I / O

为什么须要异步 I / O?

  • 用户体验

浏览器中 JavaScript 在单线程上执行,还和 UI 渲染共用一个线程

《高性能 JavaScript》曾总结过,如果脚本执行的工夫超过 100ms 用户就会感到页面卡顿

如果网页长期须要获取一个网络资源,通过同步的形式获取,JS 须要等资源齐全从服务器获取后能力继续执行,这期间 UI 将进展,不响应用户的交互行为。能够设想,这样的用户体验将会多差。

而采纳异步申请,JavaScript 和 UI 的执行都不会处于期待状态,给用户一个鲜活的页面

I / O 是低廉的,分布式 I / O 是更低廉的

只有后端可能疾速响应资源,能力让前端体验变好

  • 资源分配

计算机在倒退过程中将组件进行了形象,分为了 I / O 设施和计算设施

假如业务场景有一组互不相干的工作须要实现,支流办法有两种:

  1. 多线程并行实现

多线程的代价在于创立线程和执行线程上下文切换的开销较大。

在简单的业务中常常面临锁、状态同步等问题。然而多线程在多核 CPU 上可能无效晋升 CPU 利用率

  1. 单线程串行顺次执行

单线程程序执行工作比拟合乎编程人员按程序思考的思维形式,仍然是支流的编程形式

串行执行的毛病在于性能,任意一个略慢的工作都会导致后续执行代码被阻塞

在计算机资源中,通常 I / O 与 CPU 计算是能够并行的,同步编程模型导致的问题是,I / O 的进行会让后续工作期待,这造成资源不能更好地被利用

  1. Node 在两者之间给出了它的答案

利用单线程,远离多线程死锁、状态同步等问题;

利用异步 I / O,让单线程能够远离阻塞,更好地应用 CPU

为了补救单线程无奈利用多核 CPU 的毛病,Node 提供了相似前端浏览器中 Web Workers 的子过程,该子过程能够通过工作过程高效地利用 CPU 和 I / O

异步 I / O 的提出是冀望 I / O 的调用不再阻塞后续运算,将原有期待 I / O 实现的这段时间调配给其余须要的业务去执行

异步 I / O 现状

异步 I / O 与非阻塞 I / O

操作系统内核对于 I / O 形式只有两种:阻塞与非阻塞

在调用阻塞 I / O 时,应用程序须要期待 I / O 实现才返回后果

特点:调用之后肯定要等到零碎内核层面实现所有操作后调用才完结

例子:零碎内核在实现磁盘寻道、读取数据、复制数据到底细能力中之后,这个调用才完结》

非阻塞 I / O 与阻塞 I / O 的差异为调用之后会立刻返回

非阻塞 I / O 返回之后,CPU 的工夫片能够用来解决其余事务,此时的性能晋升是显著的

存在的问题:

  • 因为残缺的 I / O 没有实现,立刻返回的并不是业务层冀望的数据而仅仅是以后调用的状态
  • 为了获取残缺的数据,应用程序须要反复调用 I / O 操作来确认是否实现,称之为“轮询”。

次要的轮询技术

  • read

它是最原始、性能最低的一种,通过反复调用查看 I / O 的状态来实现数据的残缺读取

在失去最终数据前,CPU 始终耗用在期待上

  • select

它是在 read 的根底上改良的一种计划,通过对文件描述符上的事件状态来进行判断

限度:它采纳一个 1024 长度的数组来存储状态,最多能够同时查看 1024 个文件描述符

  • poll

较 select 有所改进,采纳链表的形式防止数组长度的限度,其次它能防止不须要的查看

文件描述符较多时,它的性能还是非常低下的

  • epoll

该计划是 Linux 下效率最高的 I / O 事件告诉机制,在进入轮询的时候如果没有查看到 I / O 事件,将会进行休眠,直到事件将它唤醒。它是实在利用了事件告诉、执行回调的形式,而不是遍历查问,所以不会节约 CPU,执行效率较高

现实的非阻塞异步 I / O

只管 epoll 曾经利用了工夫来升高 CPU 的耗用,然而休眠期间 CPU 简直是限度的,对于以后线程而言利用率不够

完满的异步 I / O 应该是应用程序发动非阻塞调用,无需通过遍历或者工夫唤醒等形式轮询

能够间接解决下一个工作,只需在 I / O 实现后通过信号或回调将数据传递给应用程序即可

Linux 下存在原生提供的一种异步 I / O 形式(AIO)就是通过信号或者回调来传递数据的

毛病:

  • 仅 Linux 下有
  • 仅反对 I / O 中的 O_DIRECT 形式读取,导致无奈利用零碎缓存

注:对于 O_DIRECT

事实的异步 I / O

通过让局部线程进行阻塞 I / O 或者非阻塞 I / O 加轮询技术来实现数据获取,让一个线程进行计算解决,通过线程之间的通信将 I / O 失去的数据进行传递,这就轻松实现了异步 I / O(只管它是模仿的

  • libeio 本质上是采纳线程池与阻塞 I / O 模仿异步 I / O
  • Node 最后在 *nix 平台下采纳 libeio 配合 libev 实现异步 I / O,后通过自行实现线程池实现
  • Windows 下的 IOCP

    • 调用异步办法,期待 I / O 实现之后的告诉,执行回调,用户无需思考轮询
    • 外部其实仍是线程池的原理,不同之处在于这些线程池由零碎内核接手治理
    • 与 Node 异步调用模型非常近似
  • 因为 Windows 平台和 *nix 平台的差别,Node 提供了 libuv 作为形象封装层,做兼容性判断

    • 保障下层 Node 与上层的自定义线程池和 IOCP 各自独立
  • 咱们时常提到 Node 是单线程的

    • 这里的单线程仅仅只是 JavaScript 执行在单线程中罢了
    • 无论是 *nix 还是 Windows 平台,外部实现 I / O 工作的另有线程池

Node 的异步 I / O

Node 实现整个异步 I / O 环节的有事件循环、观察者和申请对象等

事件循环

着重强调一下 Node 本身的执行模型——事件循环

Node 过程启动时,会创立一个相似 while(true)的循环

每次循环体的过程称之为 Tick,每个 Tick 的过程就是查看是否有事件待处理

如果有就取出事件及其相干的回调函数,并执行它们

观察者

每个事件循环中有一个或多个观察者,而判断是否有事件要解决的过程就是向这些观察者询问是否有要解决的事件

  • 浏览器采纳了相似的机制

    • 事件可能来自用户的点击或者加载某些文件时产生,而这些产生的事件都有对应的观察者
  • Node 中事件次要来源于网络申请、文件 I / O 等

    • 这些工夫对应的观察者有文件 I / O 观察者、网络 I / O 观察者等,将事件进行了分类
  • 事件循环是一个典型的生产者 / 消费者模型

    • 异步 I / O、网络申请等则是事件的生产者
    • 这些事件被传递到对应的观察者,事件循环则从观察者那取出事件并解决

小结

事件循环、观察者、申请对象、I / O 线程池这四者独特形成了 NOde 异步 I / O 模型的基本要素

因为咱们晓得 JavaScipt 是单线程的,所以按尝试很容易了解它不能充分利用多核 CPU

事实上在 Node 中,除了 JavaScript 是单线程外,Node 本身其实是多喜爱昵称的,只是 I / O 线程应用的 CPU 较少

另一个须要留神的点是,除了用户代码无奈并行执行以外,所有的 I / O 是能够并行执行的

注:图为 Node 整个异步 I / O 过程

事件驱动与高性能服务器

后面对异步的解说,也根本勾画出了事件驱动的本质,即通过主循环加事件触发的形式来运行程序

上面为几种经典的服务器模型:

  • 同步式

    • 一次只能解决一个申请,并且其余申请都处于期待状态
  • 过程 / 申请

    • 这样能够解决多个申请,然而它不具备扩展性,因为系统资源只有那么多
  • 线程 / 申请

    • 只管线程比过程要清凉,然而因为每个线程都占用肯定内存,当大并发申请到来时,内存将会很快用光,导致服务器迟缓
    • 比过程 / 申请要好,但对于大型站点而言仍然不够
  • 总结

    • 线程 / 申请的形式目前还被 Apache 所采纳
    • Node 通过事件驱动的形式解决申请,无需为每一个申请创立额定的线程,能够省掉创立线程和销毁线程的开销
    • 同时操作系统在调度工作时因为线程较少,上下文的代价很低

      • 这使得服务器可能井井有条地解决申请,即便在大量连贯的状况下,也不受上下文切换开销的影响,这也是 Node 高性能的一个起因

事件驱动带来的高效曾经慢慢开始为业界所器重

出名服务器 Nginx 也摒弃了多线程的形式,采纳和 Node 雷同的事件驱动

不同之处在于 Nginx 采纳纯 C 写成,性能较高,然而它仅适宜于做 Web 服务器,用于反向代理或者负载平衡服务,在业务解决方面较为欠缺

Node 则是一套高性能平台,能够利用它构建与 Nginx 雷同的性能,也能够解决各种具体业务

Node 没有 Nginx 在 Web 服务器方面那么业余,但场景更大,本身性能也不错

在理论我的项目中能够联合它们各自的长处以达到利用的最优性能

JavaScript 在服务器端近乎空白,使得 Node 没有任何历史包袱,而 Node 在性能优化上的体现使得它一下子就在社区中风行了起来~

写在最初

本文介绍了 Node 被发明的目标、语言选型、特点、模块机制、包管理机制以及异步 I / O 等相干常识,心愿能给你带来对 Node 有一个新的意识。最近始终也在打算学习 Node 和服务端相干常识,感兴趣的同学能够一起学习和交换~

  • 原文链接
  • 掘金:前端 LeBron
  • 知乎:前端 LeBron
    继续分享技术博文,关注微信公众号👇🏻

退出移动版