NodeJS基础知识
关于Node的火热程度和简介想必也不用多说,这里就简单的描述一下,Nodejs作者Ryan Dahl,初衷是单纯的开发一个Web服务器,但是项目的发展超出了他的预期值。为什么Node要选择JavaScript作为实现的语言,这得益于Chrome的V8引擎的出色性能,CommonJS标准的制定,符合事件驱动,还有一点JavaScript的群众基础大同时门槛相比于C/C++较低,同时尽管服务端JavaScript之前已经存在,但是没有市场,所以没有市场包袱,当然也离不开生态社区中诸多开发者无私的贡献,以上种种造就了Nodejs的火爆。
1. 为什么说Node吞吐量大?
Node的机制,保留了JavaScript在浏览器中单线程的特点,对于进来的请求不是在后台启动一个线程来单独跟踪 ,而是利用事件轮询机制处理,单独开启一个线程是需要消耗CPU内存的,比如你CPU不行,进来1000个请求,你的CPU就蹦了,所以传统的解决方案就是添加NLB负载均衡来缓解CPU的的压力。但是对于Node来说则会大大改善这一问题,因为它采用的是事件轮询回掉的机制,采用异步I/O 编程,利用单线程,原理多线程死锁,状态同步等问题,同时远离阻塞,更好的利用CPU,这也是Node吞吐量大的原因。
2. 为什么说Node速度快?
先说一下Node到底有多快,相比于静态语言C和C++还是比不过的,不过也可以说是仅次于那些静态语言。为什么这样说,之前已经简单的说了它的机制是事件轮询和链式回掉,这也是区别于传统的方式,规避掉了多线程并行的方式,减少CPU的消耗,避免了了一些数据共享与死锁的问题,其次,Node的内建模块是有C/C++编写(如下图),说明了Node模块底层的处理速度。再者是Node的缓存机制,对于内建模块再被调用时是直接转化为二进制缓存到本地。
3. 什么是异步I/O?
在说异步I/O之前先来明确几个概念,同步/异步, 阻塞/非阻塞,首先同步异步与阻塞非阻塞不是一回事儿,同步/异步没错就是你想的那样,而阻塞和非阻塞是在面向系统内核来说分为阻塞IO/非阻塞IO,区别在于是否会立即返回结果,阻塞的是等完整的IO完成之后返回完整IO,而非阻塞是立即返回IO的状态,然后通过多次重复判断来确认是否完成完整IO,这个重复判断的操作叫做轮询。其实无论阻塞和非阻塞,都有自己的缺点,期待的完美的异步IO示意图应该如下,不浪费任何CPU资源同时完成完整IO的时还能及时返回。
其实也就是Windows下IOCP异步I/O模型,Linux下是另一种相似的模型暂不去探讨,在IOCP之上,Node提供了Libuv作为抽象封装层,同时兼容多平台。
4. 什么是Node的事件轮询(Event Loop)?
在说Node事件轮询之前,咱们先了解一下JavaScript的事件轮询(在浏览器中的行为)。在JavaScript中的事件轮询说白了就是方法以参数的形式被传递,然后以同步的方式执行方法,也就是常说的callback,执行在单线程上。
还要补充先说明一下Node的单线程其实是在执行JavaScript的时候是单线程,Node自身时候有线程池的概念的,类似有web worker的子线程。Node的事件轮询是在进程启动时,Node便会创建一个类似与While(true)的循环,每执行一次循环成为一个次Tick,在Tick的过程中查看是否有事件需要被处理,如果有就取出事件和相关的callback,然后执行,再之后下一次循环重复相同动作。不过这里的事件由来是很复杂的过程,首先从我们调用开始到系统内核会一个叫做请求对象的概念,也就是libuv封装层根据具体平台创建一个适合与具体平台应用的一个对象例如windows的FSReqWrap请求对象,然后会把我们调用的方法的参数和callback函数添加到这个生成的对象的属性上,然后调用相应的底层函数,当前I/O操作也就再线程池中等待执行了,我们调用的JavaScript被函数被立即返回,不会阻塞后续的JavaScript操作,至此咱们这个调用完成了第一部分。而我们最关心的是刚才被添加到底层对象属性上的callback函数,callback函数会在什么时候执行那?其实是这样的,刚才我们的I/O操作已经是在线程池中处于被等待执行的状态,如果现在有空闲的线程就会执行我们的操作,我们的操作结果会被存在FSReqWrap的result属性上,然后调用PostQueuedCompletetionStatus()同时给IOCP,然后每次Tick的时候调用GetQueuedCompletionStatus()方法来检查县城时候是否有执行完成的请求,如果存在会将请求对象作为事件处理,这也就是上面说的事件的由来。通读之后是不是有种豁然开朗的感觉。还有如果一点callback函数的trigger是在I/O观察者的回掉方法中执行(每个事件循环都会有至少一个的观察者),取出之前咱们添加到请求对象属性上的方法进行调用。
5. Node构建Web服务器的执行流程是什么样的?
其实如果你对上面都已经理解基本上也就知道了大概的执行流程,下面的图给出一个参考。
6. NPM与包。
包和NPM是将模块之间联系起来的一种机制。如下图。CommonJS包规范定义中包含两点包结构和包描述文件。
· 包结构
package.json | 包描述文件 |
bin | 用于存放可执行二进制文件的目录 |
lib | 用于存放JavaScript代码的目录 |
doc | 用于存放文档的目录 |
test | 用于存放单元测试用例的代码 |
· 包描述文件与NPM
创建一个packgage.json文件可以手动创建与根目录下,或者用npm init命令,根据提示填写。
name | 包名 |
description | 包简介 |
version | 版本号 |
keywords | 关键词数组 |
maintainers | 包维护者 example=> “maintainers”: [{"name": "Jackson Tian", "email": "[email protected]", "web": "http://html5ify.com"}] |
contributors | 贡献者列表 |
bugs | 一个可以反馈bug的网页地址或者邮箱地址 |
licenses | 当前包所使用的许可证列表 example=> "licenses": [{"type": "GPLv2", "url": "http://www.example/licenses/gpl.html"}] |
repositories | 托管源代码的位置列表 |
dependencies | 当前包所需要的依赖包列表 |
homepage | 当前包的网站地址 |
os | 操作系统 |
cpu | CPU架构的支持列表 |
engine | 支持的JavaScript引擎列表 |
builtin | 标志当前包是否是内建在底层系统的标准组建 |
directories | 包目录说明 |
scripts | 脚本说明对象 |
author | 包作者 |
bin | 一些包作者希望包可以作为命令行工具使用 |
main | 模块引入方法require()在引入包时会优先检查这个字段,并将其作为包中其余模块的入口,如果不存在这个字段,require()方法会自动查找包目录下的index.js,index.node,index.json文件作为默认入口 |
对于Node而言NPM基于以上规范解决依赖包的安装问题,同时帮助完成了第三方模块的发布,安装和依 赖等。借助NPM,Node与第三方模块之间形成了很好的生态系统。
· NPM常用命令
npm -v 查看版本号
npm install express NPM会在当前目录下创建node_modules目录,然后在node_modules目录下创建express目录,然后把包解压到这个目录下。
npm install express -g 进行全局安装,并不是从任何地方都可以通过require()引用到它,而是需要通过软连接的方式链接到Node的可执行目录下。也就是需要在package.json的bin字段下进行配置。
npm install
npm install --registry=http://registry.url 使用镜像进行安装,如淘宝镜像需要把镜像地址替换成淘宝镜像。
npm config set registry http://registry.url 设置淘宝镜像。
npm uninstall
npm test 运行测试用例,一个优秀的包中应该包含测试用例,以便检查包是否稳定可靠。
npm adduser 发布包时的前提,你得注册了仓库管理账号。如果你还没有仓库账号就去仓库传送门吧。
npm publish 开发发布。
npm owner ls
npm owner add
npm owner rm
npm ls 分析包,列出可以通过require()引入的所有包,并生成依赖树。
至此,Node比较基础的概念和原理就先介绍到这,以上为个人理解,不到之处望海涵与指正。