1.5 Node的应用场景

在进行技术选型之前,需要了解一项新技术具体适合什么样的场景,毕竟合适的技术用在合适的场景可以起到意想不到的效果。关于Node,探讨得较多的主要有I/O密集型和CPU密集型。

1.5.1 I/O密集型

在Node的推广过程中,无数次有人问起Node的应用场景是什么。如果将所有的脚本语言拿到一处来评判,那么从单线程的角度来说,Node处理I/O的能力是值得竖起拇指称赞的。通常,说Node擅长I/O密集型的应用场景基本上是没人反对的。Node面向网络且擅长并行I/O,能够有效地组织起更多的硬件资源,从而提供更多好的服务。

I/O密集的优势主要在于Node利用事件循环的处理能力,而不是启动每一个线程为每一个请求服务,资源占用极少。

1.5.2 是否不擅长CPU密集型业务

换一个角度,在CPU密集的应用场景中,Node是否能胜任呢?实际上,V8的执行效率是十分高的。单以执行效率来做评判,V8的执行效率是毋庸置疑的。

我们将相同的斐波那契数列计算(F0=0,F1=1,Fn=F(n-1)+F(n-2)(n≥2))分别用各种脚本语言写了算法实现,并进行了n = 40的计算,以比较性能。这个测试主要偏重CPU栈操作,表1-1是其中一次运算耗时的排行。在这些脚本语言中(其中C和Go语言是静态语言,用于参考),Node是足够高效的,它优秀的运算能力主要来自V8的深度性能优化。

表1-1 计算斐波那契数列的耗时排行

语  言 用户态时间 排  名 版  本
C with -O2 0m0.202s #0 i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1
(Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Node(C++模块) 0m1.001s #1 v0.8.8, gcc -O2
Java 0m1.305s #2 Java(TM) SE Runtime Environment (build 1.6.0_35-b10-428-11M3811)
Java HotSpot(TM) 64-Bit Server VM (build 20.10-b01-428, mixed mode)
Go 0m1.667s #3 Go version go1.0.2
Scala 0m1.808s #4 Scala code runner version 2.9.2 -- Copyright 2002-2011, LAMP/EPFL
LuaJIT 0m2.579s #5 LuaJIT 2.0.0-beta10 -- Copyright (C) 2005-2012 Mike Pall.
Node 0m2.872s #6 v0.8.8
Ruby 2.0.0-p0 0m27.777s #7 ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin12.2.0]
pypy 0m30.010s #8 Python 2.7.2 (341e1e3821ff, Jun 07 2012, 15:42:54) [PyPy 1.9.0 with GCC 4.2.1]
Ruby 1.9.x 0m37.404s #9 ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin12.1.0]
Lua 0m40.709s #10 Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
Jython 0m53.699s #11 Jython 2.5.2
PHP 1m17.728s #12 PHP 5.4.6 (cli) (built: Sep 8 2012 23:49:53)
Python 1m17.979s #13 Python 2.7.2
Perl 2m41.259s #14 This is perl 5, version 12, subversion 4 (v5.12.4) built for darwin-thread-multi-2level
Ruby 1.8.x 3m35.135s #15 ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0]

这样的测试结果尽管不能完全反映出各个语言的性能优劣,但已经可以表明Node在性能上不俗的表现。从另一个角度来说,这可以表明CPU密集型应用其实并不可怕。CPU密集型应用给Node带来的挑战主要是:由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起。但是适当调整和分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞I/O调用的发起,这样既可同时享受到并行异步I/O的好处,又能充分利用CPU。

关于CPU密集型应用,Node的异步I/O已经解决了在单线程上CPU与I/O之间阻塞无法重叠利用的问题,I/O阻塞造成的性能浪费远比CPU的影响小。对于长时间运行的计算,如果它的耗时超过普通阻塞I/O的耗时,那么应用场景就需要重新评估,因为这类计算比阻塞I/O还影响效率,甚至说就是一个纯计算的场景,根本没有I/O。此类应用场景或许应当采用多线程的方式进行计算。Node虽然没有提供多线程用于计算支持,但是还是有以下两个方式来充分利 用CPU。

  • Node可以通过编写C/C++扩展的方式更高效地利用CPU,将一些V8不能做到性能极致的地方通过C/C++来实现。由上面的测试结果可以看到,通过C/C++扩展的方式实现斐波那契数列计算,速度比Java还快。

  • 如果单线程的Node不能满足需求,甚至用了C/C++扩展后还觉得不够,那么通过子进程的方式,将一部分Node进程当做常驻服务进程用于计算,然后利用进程间的消息来传递结果,将计算与I/O分离,这样还能充分利用多CPU。

CPU密集不可怕,如何合理调度是诀窍。

1.5.3 与遗留系统和平共处

有人会说:“JavaScript一统前后端了,将来会不会干掉其他的语言?”言语中充满了危机感。

在Web端,过去大多都是同步的方式编写的程序,这种串行调用下层应用数据的过程中充斥着串行的等待时间,如果采用多线程来解决这种串行等待,又或多或少地显得小题大作。在Node中,语言层面即可天然并行的特性在这种场景中显得十分有效。对于已有的稳定系统,并非意味着我们要抛弃掉。

LinkedIn在他们的移动版网站上的实践非常典型地说明了这个问题。旧有的系统具有非常稳定的数据输出,持续为传统网站服务,同时为移动版提供数据源,Node将该数据源当做数据接口,发挥异步并行的优势,而不用关心它背后是用什么语言实现的。

这方面,国内的雪球财经也有很好的实践。雪球财经是从旧有的Java项目中分离出一个子项目,在这个子项目中,没有继续采用Java/JSP而是采用Node来完成Web端的开发,使得前端工程师在HTTP协议栈的两端能够高效灵活地开发,避免了Java烦琐的表达;另一方面,又利用Java作为后端接口和中间件,使其具有良好的稳定性。两者互相结合,取长补短。

1.5.4 分布式应用

阿里巴巴的数据平台对Node的分布式应用算是一个典型的例子。分布式应用意味着对可伸缩性的要求非常高。数据平台通常要在一个数据库集群中去寻找需要的数据。阿里巴巴开发了中间层应用NodeFox、ITier,将数据库集群做了划分和映射,查询调用依旧是针对单张表进行SQL查询,中间层分解查询SQL,并行地去多台数据库中获取数据并合并。NodeFox能实现对多台MySQL数据库的查询,如同查询一台MySQL一样,而ITier更强大,查询多个数据库如同查询单个数据库一样,这里的多个数据库是指不同的数据库,如MySQL或其他的数据库。

这个案例其实也是高效利用并行I/O的例子。Node高效利用并行I/O的过程,也是高效使用数据库的过程。对于Node,这个行为只是一次普通的I/O。对于数据库而言,却是一次复杂的计算,所以也是进而充分压榨硬件资源的过程。

目录