了解Node.js机制,这就够了

使用了这么久的Node.js,之前只是知道它怎么使用,并没有去深入的了解它的机制,那么今天就来聊聊它的使用机制优缺点以及适合使用的场景

是什么

Node.jsJavaScript运行平台,底层是采用c++编写的,基于JavaScript V8引擎,意味着你可以使用JavaScript编写服务器端代码,然后交给Node.js去解释执行

特点

  • 事件驱动
  • 非阻塞异步I/O
  • 模块化
  • 单线程

运行机制解析

应用程序的瓶颈所在

应用程序的请求过程可以分为两个部分:CPU 运算和 I/O读写,CPU计算速度通常远高于磁盘读写速度,这就导致CPU运算已经完成,但是不得不等待磁盘I/O任务完成之后再继续接下来的业务。所以I/O才是应用程序的瓶颈所在,在I/O密集型业务中,I/O操作会使用大量的时间,而CPU只需很短的时候,这样就会使得性能不够好,那么一般都会使用多线程去优化,同时开启多个线程去处理请求,这样就会快很多。
但是由于一个CPU核心在一个时刻只能做一件事情,操作系统只能通过将CPU切分为时间片的方法,让线程可以较为均匀的使用CPU资源。但操作系统在内核切换线程的同时也要切换线程的上下文,当线程数量过多时,上下文切换会造成系统的很大开销,所以在大并发时,多线程结构还是无法做到强大的伸缩性。

使用Node.js解决瓶颈问题

Node.js的单线程并不是真正的单线程,只是开启了单个线程进行业务处理(cpu的运算),同时开启了其他线程专门去处理I/O。当一个指令到达主线程,主线程发现有I/O之后,直接把这个事件传给 I/O线程,不会等待I/O结束后,再去处理下面的业务,而是拿到一个状态后立即往下走,这就是单线程异步I/O
I/O操作完之后呢?Node.jsI/O 处理完之后会有一个回调事件,这个事件会放在一个事件处理队列里头,在进程启动时node会创建一个类似于While(true)的循环,它的每一次轮询都会去查看是否有事件需要处理,是否有事件关联的回调函数需要处理,如果有就处理,然后加入下一个轮询,如果没有就退出进程,这就是所谓的事件驱动

那么当我是多核CPU的时候呢?还有就是当主线程业务运算超时,岂不是来不及处理事件队列里的事件?
对于这俩个问题,首先要做的一点就是在代码编写的时候尽量避免耗时的计算,将大计算进行拆分,这样能够让主线程及时得到释放,处理消费事件队列里头的事件。其次,node.js提供了child_process模块开启子进程,理想状态下每个进程各自利用一个CPU,以此实现多核的利用,child_process提供创建子进程,以及进程状态监控,进程之间通信的API等。

一个Node.js饭店的发展历程

前面的一堆理论似乎不太好明白,最后讲一个关于饭店发展历程的故事作为结尾吧。

  • 第一年
    饭店开张,只有一个厨师(同时还兼任老板、服务员、打荷、收银员),当一个客人点餐之后,这个厨师就开始记录(服务员),然后他就开始备菜(打荷)、炒菜(厨师)、然后上菜(服务员)、收钱(收银员),这个时候即使有其他客人来了,等着吧还没忙完呢。这个厨师就这样兢兢业业,有条不紊的干着每一件事,因为每件事都是亲力亲为,都不能出错,虽然所有的事情都了然于心,但效率很低,一天只能卖出十多份饭菜。
    这是饭店单线程的第一年:
    :它没有线程上下文交换所带来性能上的开销(因为每件事都是亲力亲为);
    :无法利用多核CPU(厨房空间那么大,完全可以很多人一起干活),同时错误会引起整个应用退出,应用的健壮性值得考验(当这个厨师生病,或者有事了饭店就得停业)

  • 第二年
    这个厨师第一年赚了点钱,回到老家把表哥、表弟全拉过来,现在他们有5个人,可工作方式还是跟厨师第一年的时候一样。当客人来了,就会有一个人去记录,然后自己去厨房洗菜、切菜、炒菜、洗碗、上菜、收钱。当来了第六个客人的时候,就要等待前面的人做完所有事情才能空出一个人来接待。后来他们就想既然客户多了,厨师就得多,再回老家多叫几个兄弟吧。这时新的问题发生了,当每个厨师做完饭后就出去找客人,客人说我刚刚点餐了,然后厨师就去厨房问,刚刚是哪个表兄接待的那个客户,要是没有人接待的话,我来处理。就这样忙忙碌碌一年,他们比去年多做了好多生意,但是感觉每天客户多的时候,厨房里头乱糟糟的,总要询问这个询问那个。
    这就是饭店多线程的一年:
    :一个线程服务一个请求,线程之间可以共享数据,这样可以避免内存的浪费,可以同时处理多个请求;
    :操作系统内核在切换线程的同时也要切换线程的上下文,当线程数量过多时,时间将会被消耗在上下文切换上(厨房里头乱糟糟的,总要询问这个询问那个)。

  • 第三年
    老板娘过来了(Node.Js闪亮登场),她发现这帮厨师都在各自为战,自己拿到客户的点餐后去洗菜、切菜、洗碗、炒菜、上菜、收钱,一个人只能同时处理一个任务,而且作为厨师没必要去做洗菜、切菜、洗碗、收银之类费时的工作。
    所以老板娘把所有人进行了分工:
    老板作为厨师长(Node里头的主线程),他不再去洗碗、洗菜、切菜、炒菜、收银(我们可以把洗碗、洗菜、炒菜、切菜认为是比较耗时的I/O),他只负责将收银台小妹(观察者)拿过来的菜单分配给不同的厨师,打荷(这些人就是不同的I/O线程),吩咐下去之后他不会等菜出来再走(进入下一个轮询),又问收银台小妹还有没有菜单要做,如果有继续轮询,如果没有了休息(退出进程)。当菜做出来之后放在上菜区(回调),收银台就显示菜出来了(将回调放入事件队列),当老板查询收银员(观察者)的时候,收银员就告诉厨师长,厨师长就通知服务员(处理不同I/O的线程)上菜(完成回调),这样饭店有条不紊的运行下去,客人也越来越多了。
    有些高端客户不想在这挤,所以老板娘就想,饭店房子那么大(多核CPU),可以叫她弟弟(子进程的主线程)在这开个子店(child_process),然后发现一个收银员不够用,那就多招几个收银员(多个观察者),就这样每个分店(进程)只有一个主管(主线程),主管的弱点就是无暇顾及洗碗等杂活(I/O),只能关注业务,至于饭店能开多大(应用程序能处理多大请求),要看主管的处理能力(主线程的编程强壮度)。最后,大饭店起了个时髦的名字叫Hotel NodeJs

优缺点

优点
  1. 高并发(最重要的优点)
  2. 适合I/O密集型应用
  3. 现在新版本的Node.js已经可以使用子进程来充分利用多核CPU
缺点:
  1. 不适合CPU密集型应用;
    由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起;
    解决方案:分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞I/O调用的发起;
  2. 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃
    原因:单进程,单线程
    解决方案
    (1)Nnigx反向代理,负载均衡,开多个进程,绑定多个端口;
    (2)开多个进程监听同一个端口,使用cluster模块;
    (3)使用child_process
  3. 回调太多,代码不易读
  4. Debug不方便

使用NodeJS的场景

适合使用
  1. RESTful API
    Node.js可以充分发挥其非阻塞I/O模型以及JavaScriptJSON的功能支持(如JSON.stringfy函数)
  2. 单页面、多Ajax请求应用
    如Gmail,前端有大量的异步请求,需要服务后端有极高的响应速度
  3. 基于Node.js开发Unix命令行工具
    Node.js可以大量生产子进程,并以流的方式输出,这使得它非常适合做Unix命令行工具
  4. 流式数据
    传统的Web应用,通常会将HTTP请求和响应看成是原子事件。而Node.js会充分利用流式数据这个特点,构建非常酷的应用。如实时文件上传系统transloadit
不适合使用
  1. CPU使用率较重、I/O使用率较轻的应用
    如视频编码、人工智能等,Node.js的优势无法发挥
  2. 简单Web应用
    此类应用的特点是,流量低、物理架构简单,Node.js无法提供强大的框架

参考文档:http://www.csdn.net/article/a/2016-07-12/3358

你可能感兴趣的:(了解Node.js机制,这就够了)