NodeJS是近年来比较火的服务端JS平台,这一方面得益于其在后端处理高并发的卓越性能,另一方面在nodeJS平台上的npm、grunt、express等强大的代码与项目管理应用崛起,几乎重新定义了前端的工作方式和流程。
NodeJS的成功标志着它的强大,但是不是所有情况都适合应用NodeJS作为服务器端平台呢?
答案当然是否定的,而网上也是众说纷纭。那我们从原理出发了解一下NodeJS的适用情况。
在讲NodeJS之前我们不仿先看一下传统(以Apache为代表)的服务器端处理平台处理并发的方式。
1. Apache的多线程高并发模式
Apache是当前世界排名第一的Web服务端软件,它由于支持多线程并发而受到广大服务器技术选型者的欢迎。但发展到后来,Apache在一些WEB的大型应用中也渐渐暴露出它的缺点:阻塞。
那有的同学会奇怪,Apache不是多线程处理并发吗,为什么还会出现阻塞呢?
要明白这一点我们首先需要了解线程这个概念
1.1 什么是线程?
我们引用官方的解释:线程可以独立运行的最小的CPU单位,可以在同一个进程里并发运行,共享该进程下的内存地址空间(注意这个特点)。
我们可以看到同一个进程下的线程是会共享相同的文件和内存的(内存地址空间),所以大家可以想象,当不同的线程需要占用同一个变量时,根据先到先得的原则,先到的线程在运作时,后来的线程只能在旁边等待,也就是加入到了阻塞排队序列。所以这就是造成线程阻塞的原因。
因此,虽说 进程可以支持多个线程,
它们看似同时执行,但互相之间并不同步。一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的 变量和 对象,而且它们从同一堆中分配对象。尽管这让线程之间共享信息变得更容易,因为程序设计者必须小心,确保它们不会妨碍同一 进程里的其它线程。
了解了多线程并行的缺陷后,我们就可以更好地理解NodeJS的强大所在了。因为NodeJS是异步单线程的!
2. NodeJS的异步I/O原理
我们先来看一段Apache请求数据库的代码:
代码执行到第一行的时候线程会阻塞,等待query返回结果,然后继续处理。由于数据库查询、磁盘读写、网络通信等原因(所谓的I/O)阻塞时间会非常大(相对于CPU始终频率)。对于高并发的访问,一方面线程长期阻塞等待,另一方面为了应付新情求而不断添加新线程,会浪费大量系统资源,同时线程的增加也会也会占用大量的CPU时间来处理内存上下文切换。看看node.js怎么处理。
看到没,
就四个字:异步回调。query的第二个参数是一个回调函数,进程执行到db.query的时候不会等待结果返回,而是直接继续执行下面的语句,直到进入事件循环。当数据库执行结果返回的时候会将事件发送到事件队列,等到线程进入事件循环后才会调用之前的回调函数。更专业的说法是异步I/O。只要单线程就可以。
那为什么NodeJS做到单线程,却可以实现异步呢?在这里我们先上一幅图,直戳图中的Event queue
看到没,NodeJS的工作原理其实就是
事件循环。可以说每一条NodeJS的逻辑都是写在回调函数里面的,而回调函数都是有返回之后才异步执行的!
看到这里,你不禁会惊叹,NodeJS如果所有处理都异步,岂不是晓得飞了?错错错!当然不是,不要忘记,NodeJS实现这些的基础是
单线程。没错,单线程!一条线程扛起所有操作!
你可以想象一下,NodeJS在寒风中面对着10万并发大军,OK,没问题,上来敌人一个扔到城里,上来一个又扔到城里。城里全民皆兵,可以很好地消化这些敌人。但如果上来一个类似于张飞赵云这样的人物,老Node心里一惨,和张飞大战300回合,把他打残了,再扔到城里。那后面的10万大军就得等这300回合。。。
所以这说明什么?说明NodeJS不是没有阻塞,而是阻塞不发生在后续回调的流程,而会发生在NodeJS本身对逻辑的计算和处理。我们已经知道,NodeJS的分发能力无比强大,可以循环事件进行异步回调。但如果在循环事件时遇到复杂的逻辑运算,那么单薄的单线程怎么支撑得起上百万的逻辑+
并发呢?
NodeJS它的所有I/O、网络通信等比较耗时的操作,都可以交给worker threads执行再回调,所以很快。但CPU的正常操作,它就只能自己抗了。
说到这里,各位对NodeJS的特性估计也大概有个谱了。所以说适用的场景基本是呼之欲出了~!
3. NodeJS的应用场景
既然NodeJS处理并发的能力强,但处理计算和逻辑的能力反而很弱,因此,如果我们把复杂的逻辑运算都搬到前端(客户端)完成,而NodeJS只需要提供异步I/O,这样就可以实现对高并发的高性能处理。情况就很多啦,比如:RESTFUL API、实时聊天、客户端逻辑强大的单页APP,具体的例子比如说:本地化的在线音乐应用,本地化的在线搜索应用,本地化的在线APP等。
顺便提一下Apache,打压了这么多,给颗甜枣。Apache由于其多线程高并发共享内存地址空间的特性,那就意味着如果服务器足够强大,处理器足够高核,Apache的运作将会非常良好,所以适用于(并发)异步处理相对较少,后台计算量大,后台业务逻辑复杂的应用程序。