传统服务器与Node.js的比较
传统的服务器在面临高并发的场景时,会使用多线程方案,服务器会为客户端的请求分配一个线程,使用同步的I/O,系统通过线程切换来弥补同步I/O调用过程中的时间开销。Apache就是使用的这种方式,由于I/O操作会消耗比较多的时间,通过多线程的方式解决高并发的问题,难以实现高性能,但是可以实现很复杂的逻辑。
但是大多数网站的服务端不需要太多的计算处理,收到请求之后交给其他的服务进行处理,将处理结果返回给客户端。因此Node.js会针对这样的应用场景使用单线程模型进行处理,并不会为每一个客户端的请求创建一个线程,而是通过一个线程处理所有的请求,然后对I/O操作进行异步处理,这样的方式可以减少创建、销毁线程以及线程切换消耗的时间。
Node.js的事件处理机制
在Node.js中采用了非阻塞的I/O机制,在这种机制下应用程序所进行的处理都不会再结束之前阻碍其他处理的进行。这些处理都是相互独立的,每一个事件处理完成之后,会执行一个回调函数。
Node.js在主线程里面维护了一个事件队列,当接到请求之后就会将请求作为一个事件放在这个事件队列中,然后继续接受其他的请求。当主线程空闲的时候就开始循环事件队列。检查队列中是否有需要处理的事件,如果需要处理的事件不是I/O任务,就亲自处理,通过回调函数返回到上层调用。如果是I/O任务,就从线程池里面拿出一个线程处理这个事件,并且指定回调函数,然后继续循环队列中的其他事件。
Node.js事件环原理
事件循环定义:当线程中的I/O任务完成之后就会执行指定的回调函数,并且将这个完成的事件放在事件队列的尾部,等待事件循环,当主线程再次循环到这个事件的时候,就会直接处理并且返回给上层调用,这个过程就是事件循环(Event Loop)。Node.js运行的原理图如下所示:
这个图是整个 Node.js 的运行原理,从左到右,从上到下,Node.js 被分为了四层,分别是 应用层、V8引擎层、Node API层和LIBUV层。
* 应用层:即 JavaScript 交互层,常见的就是 Node.js 的模块,比如 http,fs。
* V8引擎层:即利用 V8 引擎来解析JavaScript 语法,进而和下层 API 交互。
* Node API层:为上层模块提供系统调用,一般是由 C 语言来实现,和操作系统进行交互。
* LIBUV层:是跨平台的底层封装,实现了 事件循环、文件操作等,是 Node.js 实现异步的核心。
在Node.js的内部是通过线程池来完成I/O操作的,但是LIBUV层会针对不同的操作系统平台的差异性实现了统一调用,Node.js的单线程指的是JavaScript运行在单线程中,并不是说Node.js是单线程的,Node.js是一个多线程的平台,但是对于JavaScript的处理是单线程的。
Node.js特点及适用性
- Node.js在处理I/O任务的时候,会把任务交给线程池来处理,高效简单,因此Node.js适合用于处理I/O密集型的任务,但是不适合处理CPU密集型的任务,这是因为对于非I/O任务Node.js都是通过主线程亲自计算的,前面的任务没有处理完的情况下就会导致后来的任务堆积,就会出现响应缓慢的情况。即使是多核CPU在使用Node.js处理非I/O任务的时候,由于Node.js只有一个事件循环队列,所以只占用一个CPU内核,但是其他的内核都会处于空闲状态,因此会造成响应缓慢,CPU资源浪费的情况,所以Node.js不适合用于处理CPU密集型的任务。
- Node.js还有一个优点是线程安全,单线程的JavaScript运行方式保证了线程的安全,不用担心同一个变量被多个线程进行读写造成程序崩溃。同时也免去了在多线程编程中忘记对变量加锁或者解锁造成隐患。