微信号:infoqchina

介绍:有内容的技术社区媒体

Node.js软肋之CPU密集型任务

2014-03-11 18:19 吴海星

Node.js在官网上是这样定义的:“一个搭建在Chrome JavaScript运行时上的平台,用于构建高速、可伸缩的网络程序。Node.js采用的事件驱动、非阻塞I/O模型使它既轻量又高效,是构建运行在分布式设备上的数据密集型实时程序的完美选择。”Web站点早已不仅限于内容的呈现,很多交互性和协作型环境也逐渐被搬到了网站上,而且这种需求还在不断地增长。这就是所谓的数据密集型实时(data-intensive real-time)应用程序,比如在线协作的白板,多人在线游戏等,这种web应用程序需要一个能够实时响应大量并发用户请求的平台支撑它们,这正是Node.js擅长的领域。


用Node.js处理I/O密集型任务相当简单,只需要调用它准备好的异步非阻塞函数就行了。然而数据密集型实时(data-intensive real-time)应用程序并不是只有I/O密集型任务,当碰到CPU密集型任务时,比如要对数据加解密(node.bcrypt.js),数据压缩和解压(node-tar),或者要根据用户的身份对图片做些个性化处理,这时候该怎么办呢?我们先来了解下Node.js自身的编程模型。


网络编程策略


上世纪90年代提出了一个著名的C10K问题。大概意思是当用户数超过1万时,很多没设计好的网络服务程序性能将急剧下降,甚至瘫痪。这时候升级硬件也不管用了,问题的根源是系统处理请求的策略,有再多的硬件资源它也用不起来。后来人们总结出了四种典型的网络编程策略:


  1. 服务器为每个客户端请求分配一个线程/进程,使用阻塞式I/O。Java就是这种策略,Apache也是,这种策略还是很多交互式应用的首选。因为阻塞,这种策略很难实现高性能,但非常简单,可以实现复杂的交互逻辑。

  2. 服务器用一个线程处理所有客户端请求,使用非阻塞的I/O及事件机制。node.js采用的就是这种策略。这种策略实现起来比较简单,方便移植,也能提供足够的性能,但无法充分利用多核CPU资源。

  3. 服务器会分配多个线程来处理请求,但每个线程只处理其中一组客户端的请求,使用非阻塞的I/O及事件机制。这是对第二种策略的简单改进,在多线程并发上容易出现bug。

  4. 服务器会分配多个线程来处理请求,但每个线程只处理其中一组客户端的请求,使用异步I/O。这种策略在支持异步I/O的操作系统上性能非常高,但实现起来很难,主要用在windows平台上。


因为大多数网站的服务器端都不会做太多的计算,它们只是接收请求,交给其它服务(比如文件系统或数据库),然后等着结果返回再发给客户端。所以聪明的Node.js针对这一事实采用了第二种策略,它不会为每个接入请求繁衍出一个线程,而是用一个主线程处理所有请求。避开了创建、销毁线程以及在线程间切换所需的开销和复杂性。这个主线程是一个非常快速的event loop,它接收请求,把需要长时间处理的操作交出去,然后继续接收新的请求,服务其他用户。下图描绘了Node.js程序的请求处理流程:



主线程event loop收到客户端的请求后,将请求对象、响应对象以及回调函数交给与请求对应的函数处理。这个函数可以将需要长期运行的I/O或本地API调用交给内部线程池处理,在线程池中的线程处理完后,通过回调函数将结果返回给主线程,然后由主线程将响应发送给客户端。那么event loop是如何实现这一流程的呢?这要归功于Node.js平台的V8引擎和libuv。


Event Loop和Tick


每个Node程序的主线程都有一个event loop,JavaScript代码全在这个单线程下运行。所有的I/O操作以及对本地API的调用,或者是异步的(借助程序所在平台的机制),或者运行在另外的线程中。这全都是通过libuv处理的。所以当socket上有数据过来,或本地API函数返回时,需要有种同步的方式调用对刚发生的这一特定事件感兴趣的JavaScript函数。


在发生事件的线程中直接调用JS函数是不安全的,因为那样也会遇到常规多线程程序遇到的问题,竞态条件、非原子操作的内存访问等等。所以要以一种线程安全的方式把事件放在队列中。


当Event loop遇到CPU密集型任务


因为event loop在处理所有的任务/事件时,都是沿着事件队列顺序执行的,所以在其中任何一个任务/事件本身没有完成之前,其它的回调、监听器、超时、nextTick()的函数都得不到运行的机会,因为被阻塞的event loop根本没机会处理它们,此时程序最好的情况是变慢,最糟的情况是停滞不动,像死掉一样。所以当Node.js遇到高CPU占用率的任务时,event loop会被阻塞住,形成下面这种局面:



在分析了Node.js不擅于处理CPU密集型任务的原因之后,本文给出了加强Node.js处理CPU密集型任务能力的几种办法。


更多精彩内容,请点击阅读原文。


***********************************

本文来自InfoQ微信公众账号:infoqchina

1、回复“今日新闻”,查看今天更新的新闻;

2、回复“今日英文”,查看今天英文站的更新;

3、回复“文章 +关键词”,搜索关键词相关内容;

4、回复“QCon”,了解QCon大会相关信息;

5、回复“活动”,了解最近InfoQ组织的线下沙龙;

6、回复“架构师”,获取《架构师》下载地址;

7、回复“投稿”,了解投稿和加入编辑团队的流程。

***********************************

 
InfoQ 更多文章 Facebook如何实现PB级别数据库自动化备份 学术派Google软件工程师Matt Welsh谈移动开发趋势 Spotify为什么要使用一些“无聊”的技术? 妹纸们放假了,汉纸们做啥? 大多数重构可以避免
猜您喜欢 Java语言实现的单层感知器 Python使用模糊哈希值比较文件相似度 17个提升iOS开发效率的必用工具 如何使用ELK来监控性能 据说这是年度最佳安卓机……