NodeJS
NodeJS 4个特点:异步I/O,事件驱动与回调,单线程事件轮询,跨平台。
NodeJS 5个大坑:异常处理,嵌套太深,没有Sleep,多线程编程,异步转同步。
NodeJS 4种提升性能的方法:动静分离,缓存(Redis),多进程,数据库读写分离。
NodeJS简介 模块机制 异步I/O 异步编程 内存控制 Buffer 网络编程 构建Web 多进程 测试 产品化 调试NodeJS 编程规范
NodeJS简介
高性能,符合时间驱动,没有历史包袱这三个主要原因,JavaScript成为了Node的实现语言。 |
NodeJS基于Google V8引擎。Node优秀的运算能力主要来自V8的深度优化。 |
NodeJS特点:异步I/O,事件驱动与回调函数,单线程事件轮询,跨平台 |
NodeJS单线程的缺点: 无法利用多核CPU. 错误会引起整个应用进程退出。 大量计算占用CPU导致无法继续调用异步I/O.
解决单线程缺点的方法是引入子进程方法(Cluster,见后边)和C/C++模块扩展(利用它们的多线程机制)。 |
模块机制
Node出现之前,服务器端的JS基本没有市场的。 |
CommonJS主要是为了弥补当前JS没有标准的缺陷,希望JS能够在任何地方运行。 |
模块引用 var xxx = require(‘模块标识’); 例如:var math = require(‘math’); |
模块定义 模块中module对象代表模块自身,exports对象是module的属性,用于导出当前模块的方法或者变量,它是唯一导出的出口。module和exports是node在编译过程中给加上去的。 NodeJS中,一个文件就是一个模块,将方法或者变量挂在在exports对象上作为属性即可定义导出的方式: exports.xxx = …… 例如: exports.add = function(){ var sum = 0; var i = 0; …. return sum; } (exports和module.exports区别见后) |
模块标识 就是传给require()的参数。它必须符合小驼峰命名的字符串,或者以。,。。开头的相对路径,或者绝对路径。可以有或者没有文件后缀名(最好有后缀,p/17). 模块标识分类: · 核心模块,如http, fs, path等。 · .或者..开始的相对路径文件模块。 · 以/开始的绝对路径文件模块。 · 非路径形式的文件模块,如自定义的connect模块。 |
Node中引入模块步骤 (找文件->找文件扩展名->编译执行) 1. 路径分析:定位文件位置,标识符中有路径,甚至没有。 2. 文件定位:标识符中没有文件扩展名,所以需要确定类型。 3. 编译执行:不同文件类型,载入方式不一样。 |
Node中模块分类 1. 核心模块(Node提供):核心模块在Node源代码的编译过程中,编译进了二进制文件,在Node进程启动时,部分核心模块就被加载进内存中,所以这部分核心模块引入时,文件定位和编译执行两步省略掉,且在路径分析时优先判断,所以核心模块加载速度是最快的。 2. 文件模块(用户编写):在运行时动态加载,三步骤都需要。 |
Node对引用过的模块都会进行缓存,以减少二次引入时的开销。 |
模块路径分析策略 即node在定位文件模块的具体文件时指定的查找策略。其策略是从当前node-modules开始一级一级向根的node-modules查找,核心模块最快,相对、绝对路径模块次之,自定义文件模块最慢。 所以引用模块时,最好加上路径以及扩展名! |
文件定位策略 因为模块标识符中可以不包含文件扩展名,在这种情况下,Node会按照.js, .json, .node的次序补足扩展名,依次尝试。在尝试的过程中,需要调fs模块的同步阻塞式的判断文件是否存在,所以最好传入文件扩展名。 require()通过分析文件扩展之后,可能没找到对应的文件,但却得到了一个目录,此时node会将目录当做一个包来出来(node对包处理会遵守CommonJS包规范)。 |
Node中,每个文件模块都是一个对象,定位到具体的文件后,Node会新建一个模块对象,然后根据路径载入并编译。不同的文件扩展名,其载入和编译的方法也不一样: .js文件:通过fs模块同步读取文件后编译执行。 .node文件:这是C/C++编写的扩展文件,通过process.dlopen()方法加载和执行。它不需要编译,因为它是编写C/C++模块之后编译生成的. C/C++带来的优势主要是执行效率方面的。 .json文件:通过fs模块同步读取文件后,用JSON.parse()解析返回结果。 .其余扩展名文件:当做.js文件处理。 |
JS模块中require, exports, module这三个变量在模块中并没有定义,是node在编译过程中给加上的,这样每个模块中这三个变量的作用域是隔离的。 |
exports和module.exports区别: exports指向module.exports的引用, require()返回的是module.exports而不是exports.如果module.exports指向了一个新对象则exports则断开了对module.exports的引用。刚开始module.exports为空对象,所以exports收集的属性和方法都赋给module.exports,而一旦module.exports有了属性,方法,则exports收集的信息将被忽略,所以在不调用module.exports时,采用exports. |
Node的核心模块在编译成可执行文件的过程中被编译进了二进制文件。核心模块分为C/C++编写的和JS编写的两部分。而在编译所有的C/C++文件之前,编译程序需要将所有的JS模块文件编译为C/C++代码。 |
內建模块 C++模块完成主内完成核心,JS主外实现封装的模式是node能够提高性能的常见方式。由纯C/C++编写的部分统一称为內建模块,因为他们通常不被用户直接调用。 |
GYP – Generate your projects 项目生成工具,可用于生成各种平台下的项目文件,node中安装npm install –g node-gyp |
实现C/C++扩展模块(书p/29-31) 第一步:编写C/C++源文件 第二步:创建.gyp项目文件,通过GYP命令进行build生成xxx.node文件。 第三步:通过require加载生成的.node文件,调用模块中的方法。 |
Node中文件模块,核心模块,內建模块,C/C++扩展模块之间的关系 |
包 包和NPM是将模块联系起来的一种机制 包组织模块示意图: 包实际上是一个存档文件,即一个目录直接打包为.zip,安装解压还原为目录。 包结构: package.json:包描述文件。(详细说明p/35) bin:用于存放可执行二进制文件的目录。 Lib:用于存放js代码的目录。 Doc:存放文档的目录。 Test:存放单元测试用例的代码。 |
NPM 帮助完成第三方模块的发布,安装和依赖等。 NPM版本: npm –v NPM帮助: npm
使用NPM安装依赖包: 全局安装: npm install –g express –generator,然后再用–g安装。 本地安装: npm install package.json or npm install xxx (会自动上网上搜) 非官方源安装。
NPM钩子命令:即在package.json的scripts字段定义安装前,中,后执行的脚本,完后在npm install package.json时候会调用。 如 “scripts”:{ “preinstall”: “preinstall.js”, “install”: ”install.js”, “uninstall”: “uninstall.js”, “test”: “test.js” //当执行npm test时会执行该脚本 }
使用NPM发布包:略。
使用npm ls分析包 可以分析出当前路径下能够通过模块路径找到的所有包,并生成依赖树。
为了同时能够共享npm上总多的包的,同时对自己的包进行保密和限制,现有的解决方案就是企业搭建自己的npm仓库。
Npm平台上,每个人都可以分享包到平台上,但是开发人员水平不一,上面的包质量良莠不齐。另一个问题是node代码可以运行在服务器端,需考虑安全问题。 |
异步I/O
单线程同步编程模型会阻塞I/O导致硬件资源得不到更优的使用。多线程编程模型也因为编程中的死锁,状态同步等问题让开发人员头疼。Node在两者之间给出了答案:利用单线程,远离多线程死锁,状态同步等问题;利用异步I/O,让单线程远离阻塞,以更好地使用CPU。 |
异步I/O调用示意图图3-1 p/50 |
阻塞I/O造成CPU等待资源浪费,非阻塞带来的麻烦却是需要轮询去确认是否完全完成数据获取,它会让CPU处理状态判断,是对CPU资源的浪费。 |
Node是单线程的,这里的单线程仅仅只是JavaScript执行在单线程中罢了。在node中,无论Linux还是Windows平台,内部完成I/O任务的另有线程池。+图3.10 p/55 |
事件循环 在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们成为Tick。每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行他们。然后进入下一个循环,如果不再有事件处理,就退出进程。图3-11 p/56
每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件。事件可能来自用户的点击或者加载某些文件时产生,而这些产生的文件都有对应的观察者。 |
请求对象 JS把要调用的函数,参数,回调函数这些东西封装在一个请求对象中,传递给底层的C++ I/O线程池,完后立即返回。 |
事件循环,观察者,请求对象,I/O线程池这四者共同构成了Node异步I/O模型的基本要素。 |
setTimeout()和setInterval()用于单次和多次执行任务,不需要I/O线程池参与。非精确定时。 |
process.nextTick(函数)–即下一个Tick(事件循环)异步执行一个任务,它比setTimeout(xxx,0)更高效。 function foo(){…} process.nextTick(foo) |
setImmediate()与process.nextTick()类似,都是将回调函数延迟执行。 二者区别: |