学习和使用Node.js已经有两个月,使用express结合mongoose写了一个web应用和一套RESTful web api,回过头来看Node.js官网首页对Node.js的介绍:Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.那么其中的non-blocking I/O model** 意味着什么呢?node
##非阻塞的IO模型web
首先,IO操做无疑是耗时的,当服务器端接收到大量请求时,为每个请求建立进程或线程的同时,也增长了额外的内存开销,也可能浪费更多的时间资源。express
因为Node.js是事件驱动的,因而它使用了事件循环来解决IO操做带来的瓶颈问题。在Node.js中,一个IO操做一般会带有一个回调函数,当IO操做完成并返回时,就会调用这个回调函数,而主线程则继续执行接下来的代码。简单的用一个例子来讲明这个问题:api
request('http://www.google.com', function(error, response, body) { console.log(body); }); console.log('Done!');
这段代码的意思是向'http://www.google.com'发出请求,当请求返回这则调用回调函数输出响应信息。因为Node.js的运行机制,这段代码运行后,会当即在控制台输出'Done!',而后一段时间后再输出响应的信息。服务器
##事件循环 event loop架构
接下来,来讨论下事件循环的机制。首先说说__调用桟__,好比有以下一段代码:并发
function A(arg, func){ var a = arg; func(); console.log('A'); } function B(){ console.log('B'); } A(0, B);
当代码执行后,函数A首先被推入调用桟中成为栈顶元素并开始执行A,在执行过程当中函数B又被推入调用桟成为栈顶元素,在B执行完成后,B被弹出调用桟,A再次成为栈顶元素,在A执行完成后A被弹出调用桟,调用桟呈空闲状态。mongoose
在Javascript运行时中存在一个__消息队列__,而消息和一个回调函数相关联,当一个事件被触发时,若是这个事件有相应的回调函数,则该消息就会被加入到消息队列中去。ide
回过头来讲事件循环到底循环的是什么,在代码开始执行后,函数被不断推入调用桟中,就拿上面的例子来说,request被推入调用桟中,这个函数将进行一个http请求(这个http请求将交由Node.js的底层模块来实现)同时请求完成的事件和一个回调函数关联起来,request被弹出调用桟,console.log被推入调用桟开始执行。当请求完成时,完成事件被触发,一条消息被添加进消息队列中,消息队列首先会检查调用桟是否为空闲状态,若是调用桟并不空闲,则会一直等待到调用桟空闲状态后,将消息队列的头部弹出,此时与该消息相关联的回调函数被执行。函数
##小结
以上就无阻塞模型和事件循环在概念上进行了总结。而这个事件循环的机制并不单单是Node.js所独有的,而且Node.js的代码是单线程执行的,在面对大量并发请求的时候,又有着什么优点呢?
上面这张图展现了Node.js的架构图,Node.js的底层有一个模块负责维护线程池,当一个IO请求发出的时候,Node.js的底层模块将新建一个线程来处理请求,完成后再将结果交还给上层。那么,当有多个请求的时候,Node.js的底层模块将利用尽量少的线程来完成最多的任务,若是存在空闲的线程,它将继续被利用来作其余的事情,这对于前面说的针对每一个请求开一个新的进程或线程而言,无疑“聪明”许多,也更加高效了。
这篇文章是对学习Node.js的一个总结,其中如有问题和不足,欢迎批评指正。
参考: