转自infoQ:http://www.infoq.com/cn/articles/what-is-nodejs

Node.js不是JS应用、而是JS运行平台

看到Node.js这个名字,初学者可能会误以为这是一个Javascript应用,事实上,Node.js采用C++语言编写而成,是一个Javascript的运行环境。为什么采用C++语言呢?据Node.js创始人Ryan Dahl回忆,他最初希望采用Ruby来写Node.js,但是后来发现Ruby虚拟机的性能不能满足他的要求,后来他尝试采用V8引擎,所以选择了C++语言。既然不是Javascript应用,为何叫.js呢?因为Node.js是一个Javascript的运行环境。提到Javascript,大家首先想到的是日常使用的浏览器,现代浏览器包含了各种组件,包括渲染引擎、Javascript引擎等,其中Javascript引擎负责解释执行网页中的Javascript代码。作为Web前端最重要的语言之一,Javascript一直是前端工程师的专利。不过,Node.js是一个后端的Javascript运行环境(支持的系统包括*nux、Windows),这意味着你可以编写系统级或者服务器端的Javascript代码,交给Node.js来解释执行,简单的命令类似于:

[codesyntax lang="bash"]
#node helloworld.js
[/codesyntax]

Node.js采用了Google Chrome浏览器的V8引擎,性能很好,同时还提供了很多系统级的API,如文件操作、网络编程等。浏览器端的Javascript代码在运行时会受到各种安全性的限制,对客户系统的操作有限。相比之下,Node.js则是一个全面的后台运行时,为Javascript提供了其他语言能够实现的许多功能。

Node.js采用事件驱动、异步编程,为网络服务而设计

事件驱动这个词并不陌生,在某些传统语言的网络编程中,我们会用到回调函数,比如当socket资源达到某种状态时,注册的回调函数就会执行。Node.js的设计思想中以事件驱动为核心,它提供的绝大多数API都是基于事件的、异步的风格。以Net模块为例,其中的net.Socket对象就有以下事件:connect、data、end、timeout、drain、error、close等,使用Node.js的开发人员需要根据自己的业务逻辑注册相应的回调函数。这些回调函数都是异步执行的,这意味着虽然在代码结构中,这些函数看似是依次注册的,但是它们并不依赖于自身出现的顺序,而是等待相应的事件触发。事件驱动、异步编程的设计,重要的优势在于,充分利用了系统资源,执行代码无须阻塞等待某种操作完成,有限的资源可以用于其他的任务。此类设计非常适合于后端的网络服务编程,Node.js的目标也在于此。在服务器开发中,并发的请求处理是个大问题,阻塞式的函数会导致资源浪费和时间延迟。通过事件注册、异步函数,开发人员可以提高资源的利用率,性能也会改善。

从Node.js提供的支持模块中,我们可以看到包括文件操作在内的许多函数都是异步执行的,这和传统语言存在区别,而且为了方便服务器开发,Node.js的网络模块特别多,包括HTTP、DNS、NET、UDP、HTTPS、TLS等,开发人员可以在此基础上快速构建Web服务器。以简单的helloworld.js为例:

[codesyntax lang="javascript"]
var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(80, "127.0.0.1");
[/codesyntax]

上面的代码搭建了一个简单的http服务器(运行示例部署在http://helloworld.cnodejs.net/中,读者可以访问),在本地监听80端口,对于任意的http请求,服务器都返回一个头部状态码为200、Content-Type'值为text/plain'的”Hello World“文字响应。从这个小例子中,我们可以看出几点:

  • Node.js的网络编程比较便利,提供的模块(在这里是http)开放了容易上手的API接口,短短几行代码就可以构建服务器。
  • 体现了事件驱动、异步编程,在createServer函数的参数中指定了一个回调函数(采用Javascript的匿名函数实现),当有http请求发送过来时,Node.js就会调用该回调函数来处理请求并响应。当然,这个例子相对简单,没有太多的事件注册,在以后的文章中读者会看到更多的实际例子。

Node.js的特点

下面我们来说说Node.js的特点。事件驱动、异步编程的特点刚才已经详细说过了,这里不再重复。

Node.js的性能不错。按照创始人Ryan Dahl的说法,性能是Node.js考虑的重要因素,选择C++和V8而不是Ruby或者其他的虚拟机也是基于性能的目的。Node.js在设计上也是比较大胆,它以单进程、单线程模式运行(很吃惊,对吧?这和Javascript的运行方式一致),事件驱动机制是Node.js通过内部单线程高效率地维护事件循环队列来实现的,没有多线程的资源占用和上下文切换,这意味着面对大规模的http请求,Node.js凭借事件驱动搞定一切,习惯了传统语言的网络服务开发人员可能对多线程并发和协作非常熟悉,但是面对Node.js,我们需要接受和理解它的特点。由此我们是否可以推测出这样的设计会导致负载的压力集中在CPU(事件循环处理?)而不是内存(还记得Java虚拟机抛出OutOfMemory异常的日子吗?),眼见为实,不如来看看淘宝共享数据平台团队对Node.js的性能测试

  • 物理机配置:RHEL 5.2、CPU 2.2GHz、内存4G
  • Node.js应用场景:MemCache代理,每次取100字节数据
  • 连接池大小:50
  • 并发用户数:100
  • 测试结果(socket模式):内存(30M)、QPS(16700)、CPU(95%)

从上面的结果,我们可以看到在这样的测试场景下,qps能够达到16700次,内存仅占用30M(其中V8堆占用22M),CPU则达到95%,可能成为瓶颈。此外,还有不少实践者对Node.js做了性能分析,总的来说,它的性能让人信服,也是受欢迎的重要原因。既然Node.js采用单进程、单线程模式,那么在如今多核硬件流行的环境中,单核性能出色的Node.js如何利用多核CPU呢?创始人Ryan Dahl建议,运行多个Node.js进程,利用某些通信机制来协调各项任务。目前,已经有不少第三方的Node.js多进程支持模块发布,专栏后面的文章会详细讲述Node.js在多核CPU下的编程。

Node.js的另一个特点是它支持的编程语言是Javascript。关于动态语言和静态语言的优缺点比较在这里不再展开讨论。只说三点:

    1. Javascript作为前端工程师的主力语言,在技术社区中有相当的号召力。而且,随着Web技术的不断发展,特别是前端的重要性增加,不少前端工程师开始试水”后台应用“,在许多采用Node.js的企业中,工程师都表示因为习惯了Javascript,所以选择Node.js。
    2. Javascript的匿名函数和闭包特性非常适合事件驱动、异步编程,从helloworld例子中我们可以看到回调函数采用了匿名函数的形式来实现,很方便。闭包的作用则更大,看下面的代码示例:
      [codesyntax lang="javascript"]
      var hostRequest = http.request(requestOptions,function(response) {
          var responseHTML ='';
          response.on('data', function (chunk) {
              responseHTML = responseHTML + chunk;
          });
          response.on('end',function(){
              console.log(responseHTML);
              // do something useful
         });
      });
      [/codesyntax]
      在上面的代码中,我们需要在end事件中处理responseHTML变量,由于Javascript的闭包特性,我们可以在两个回调函数之外定义responseHTML变量,然后在data事件对应的回调函数中不断修改其值,并最终在end事件中访问处理。
    3. Javascript在动态语言中性能较好,有开发人员对Javacript、Python、Ruby等动态语言做了性能分析,发现Javascript的性能要好于其他语言,再加上V8引擎也是同类的佼佼者,所以Node.js的性能也受益其中。

附: