前言

  1. 提到node.js,我们首先得明确一点,node.js不是像js,是一门语言,node是一个服务器端。node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,简单的说 node.js 就是运行在服务端的 JavaScript

node.js简介

  1. node.js是一个事件驱动I/O服务端JavaScript环境,也是一个可以让JavaScript运行在浏览器之外的平台。基于GoogleV8引擎,V8引擎执行Javascript的速度非常快,性能非常好。这里我们可能会疑惑什么是V8引擎呢?它又起到什么作用呢?<br /> V8 JavaScript引擎是Google用于其Chrome浏览器的底层JavaScript引擎。实际上,JavaScript引擎负责解释并执行代码。Google使用V8创建了一个用C++编写的超快解释器,该解释器拥有另一个独特特征;可以下载该引擎并将其嵌入任何应用程序。V8 JavaScript引擎并不仅限于在一个浏览器中运行。因此,Node实际上会使用Google编写的V8 JavaScript引擎,并将其重建为可在服务器上使用。

事件驱动

  1. 在了解node.js运行原理之前,我们得弄清楚什么是事件驱动的概念。要理解事件驱动程序,就需要与非事件驱动的程序进行比较。实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的。早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,会不断地检查这个条件,直到条件满足,这样很浪费cpu时间。而事件驱动的程序则是为需要处理的事件编写相应的事件处理程序,代码在事件发生时执行,有机会释放cpu从而进入睡眠态(注意是有机会,程序也可自行决定不释放cpu),当事件触发时被操作系统唤醒,这样就可以更有效的利用cpu

运行原理

  1. 实际上我们请求过程可以分为两个部分:cpu运算和I/O读写,cpu运算时远高于磁盘读写速度,这就可能导致从cpu运算已经完成,但是不得不等待磁盘I/O任务完成之后再继续接下来的业务。 I/O密集型业务中,假设请求需要100ms来完成,其中99ms化在I/O上。如果需要优化应用程序,让他能同时处理更多的请求,我们会采用多线程,同时开启100个、1000个线程来提高我们请求处理, 但是由于一个CPU核心在一个时刻只能做一件事情,操作系统只能通过将CPU切分为时间片的方法,让线程可以较为均匀的使用CPU资源。但操作系统在内核切换线程的同时也要切换线程的上线文,cpu会不断的在线程的上下文之间切换,会增加服务器的负担,多线程的创建和删除也会产生内存上的负担。当一个线程去完成一个业务的时候如果遇到IO操作还是会阻塞线程。<br /> 但是nodejs的处理方式是孑然不同,因为它的特点是单线程,异步IO,事件循环。<br /> 先说nodejs的单线程。关于它的单线程,有一个最直观的理解就是开发者不能自己新建线程去处理一个任务,对于web应用所有的请求的处理都是在一个主线程中完成的,所有请求的处理和请求的响应都是在一个线程中。这样的话当前的cpu只对当前的任务进行处理,不会同时处理多个请求,自然也就不需要像java那样需要锁,加锁,解锁,死锁各种问题。<br /> 但是node.js的单线程并不是真正的单线程,只是开启了单个线程进行业务处理(cpu的运算),同时开启了其他线程专门处理I/O。当主线程处理请求遇到io操作的时候不会由主线程自己去完成(因为这样会造成主线程的阻塞),当一个指令到达主线程,主线程发现有I/O之后,直接把这个事件传给I/O线程,不会等待I/O结束后,再去处理下面的业务,而是拿到一个状态后立即往下走。<br /> 线程完成之后交给事件循环,主线程通过事件循环来拿io操作的结果,然后通过回调函数来进行处理。node.jsI/O 处理完之后会有一个回调事件,这个事件会放在一个事件处理队列里头,在进程启动时node会创建一个类似于While(true)的循环,它的每一次轮询都会去查看是否有事件需要处理,是否有事件关联的回调函数需要处理,如果有就处理,然后加入下一个轮询,如果没有就退出进程,这就是所谓的“事件驱动”。这也从Node的角度解释了什么是”事件驱动”。 <br /> <br /> 这样nodejs可以不断的接受请求,但是不立即返回结果,请求的处理,io的操作交给异步I/O去处理,处理之后再返回。这样不会产生阻塞,也不会拒绝请求或者让请求等待。但是必须要注意,绝对不能让node做太多的业务逻辑,他只适合接收生成好的数据,然后或渲染后,或直接发送到客户端。

event.jpg
node.js运行原理图

  1. 由上图所示,nodejs主线程在遇到I/O操作的时候会异步多线程去处理I/O,在主线程的业务执行完之后,开始事件循环,然后通过事件循环来得到I/O处理的结果,然后主线程通过回调函数完成I/O操作之后的业务,将处理的结果返回给客户端,再去事件循环找I/O操作的返回结果,这样一直循环下去。<br /> <br /> 最后再补充一个小知识点!!!关于单线程的异步和非阻塞的理解。nodejs底层访问I/O是多线程的,阻塞/非阻塞与异步/同步是两个不同的概念,同步不代表阻塞,但是阻塞肯定就是同步;为了方便大家的理解,以下是为了解异步/同步,阻塞/非阻塞概念的几个小例子。
  • 同步情况:我去食堂打饭,我选择了A套餐,然后工作人员帮我去配餐,如果我就站在旁边,等待工作人员给我配餐,这种情况就称之为同步;
  • 同步非阻塞情况:若工作人员帮我配餐的同时,排在我后面的人就开始点餐,这样整个食堂的点餐服务并没有因为我在等待A套餐而停止,这种情况就称之为非阻塞。这个例子就简单说明了同步但非阻塞的情况。
  • 异步非阻塞情况:如果我在等待配餐的时候去买饮料,等听到叫号再回去拿套餐,此时我的饮料也已经买好,这样我在等待配餐的同时还执行了买饮料的任务,叫号就等于执行了回调,就是异步非阻塞情况。
  • 阻塞情况:如果我在买饮料的时候,已经叫我的号让我去拿套餐,可是我等了好久才拿到饮料,所以我可能在大厅叫我的餐号之后很久才拿到A套餐,这也就是单线程的阻塞情况。