Node是使用C++语言写的一款JavaScrip解析器。
高并发
一般来说,高并发的解决方案就是多线程模型,服务器为每隔客户端请求分配一个线程,使用同步I/o,比如Apache就是这种策略,由于I/O一般都是耗时操作,因为这种策略很难实现高性能,但非常简单,可以实现复杂的交互逻辑。
而事实上,大多数网站的服务器都不会做太多计算,它们只是接收请求,交给其他服务(比如从数据库读取数据),然后等着结果返回再发给客户端。因此Node.js针对这一事实采用了单线程模型来处理,它不会为每个接入请求分配一个线程,而是用一个主线程处理所有的请求,然后对I/O操作进行了异步处理,避开了创建、销毁线程以及在线程间切换所需的开销和复杂性。
Node.js使用场景
RESTful API:这是适合Node的理想情况,因为您可以构建它来处理数万条连接。它仍然不需要大量逻辑;它本质上只是从某个数据库中查找一些值并将它们组成一个响应。由于响应是少量文本,入站请求也是少量的文档,因此流量不高,一台机器甚至也可以处理最繁忙的公司的API需求。
实时程序:比如聊天服务,聊天应用程序是最能体现Node.js优点的例子:轻量级、高流量并且能良好的应对跨平台设备上运行密集型数据(虽然计算能力低)。同时,聊天也是一个非常值得学习的用例,因为它很简单,并且涵盖了目前位置一个典型的Node.js会用到的大部分解决方案。
Node.js的软肋
至此,我们对Node.js应该有了一个简单而又清晰的认识,但Node.js并不是说明都能做。
上面提到,如果是I/O任务,Nodejs就把任务交给线程池来异步处理,高效简单,因此Node.js适合处理I/O密集型任务,当碰到CPU密集型的任务时,就是只用CPU计算的操作,比如要对数据加解密(node.bcrypt.js),数据压缩和解压(node-tar),这时Node.js就会亲自处理,一个一个的计算,前面的任务没有执行完,后面的任务只能干等着,如下图所示:
在事件队列中,如果前面的CPU计算任务没有完成,那么后面的任务就会被阻塞,出现响应缓慢的情况,如果操作系统本身就是单核,那也就算了,但现在大部分服务器都是多CPU或多核的,而Node.js只有一个EventLoop,也只占用一个CPU内核,当Node.js被CPU密集型任务占用,导致其他任务被阻塞时,却还有CPU/内核处于空闲状态,造成资源浪费,因此Node.js并不适合CPU密集型任务。
Node.js哪些操作会交给线程池?
网络调用、文件系统任务、DNS 查找等异步完成的事情实际上并不由主线程处理。
CPU密集型场景的解决方案
关于CPU密集型应用,Node的一部I/O已经解决了在单线程上CPU与I/O之间阻塞无法重叠利用的问题,I/O阻塞造成的性能浪费远比CPU的影响小。对于长时间运行的计算,如果它的耗时超过了普通阻塞I/O的耗时,那么应用场景就需要重新评估,因为这类计算比I/O阻塞还影响效率,甚至说就是一个纯计算的场景,根本没有I/O。此类应用场景或许应当采用多线程的方式进行计算。Node虽然没有提供多线程用于计算支持,但是还是有以下两个方式来充分利用CPU。
- 通过编写C/C++扩展的方式更高效地利用CPU,将一些V8不能做到性能极致的地方通过C/C++来实现。由上面的测试结果可用看到,通过C/C++扩展的方式实现的计算,速度比JAVA快。
- 如果单线程的Node不能满足需求,甚至用来C/C++扩展后还觉得不够,那么通过子进程的方式,将一部分Node进程当作常驻服务进程用于计算,然后利用进程间的消息来传递结果,将计算与I/O分离,这样还能充分利用多核CPU。
异步I/O和事件驱动
异步I/O,事件驱动和单线程,它们是Node的基调。
Node.js实现异步的核心是事件驱动。也就是说,它把每一个任务都当成事件来处理,然后通过Event Loop模拟了异步的效果。
总结以上过程我们发现,Node.js的主线程就是一个单线程,它接收请求后并没有直接做处理,而是放到了事件队列中,然后去接收其他请求了,空闲时再通过Event Loop来处理这些事件,从而实现了异步的效果。当然对于IO类任务还要依赖于系统层面的线程池来处理。因为,我们可以简单理解为:Node.js本身是一个多线程平台,而它对JS层面的任务处理是单线程。
非阻塞I/O返回后,CPU得时间片可用用来处理其他事务,此时得性能提升是明显得。但非阻塞I/O也存在一些问题。由于完整得I/O并没有完成,立即返回得并不是业务层期望得数据,而仅仅是当前调用得状态。为了获取完整得数据,应用程序需要重复调用I/O操作来确认是否完成。这种重复调用判断操作是否完成的技术叫做轮询。
任何技术都并非完美的,阻塞I/O造成CPU等待浪费,非阻塞带来的麻烦却是需要轮询去确认是否完全完成数据获取,它会让CPU处理状态判断,是对CPU资源的浪费,我们且看轮询技术是如何演进的,以减少I/O状态判断的CPU损耗。