Pages

Wednesday, 11 April 2012

深入浅出Node.js

Node.js从2009年诞生至今,已经发展了两年有余,其成 长的速度有目共睹。从在github的访问量超过Rails,到去年底Node.jsS创始人Ryan Dalh加盟Joyent获得企业资助,再到今年发布Windows移植版本,Node.js的前景获得了技术社区的肯定。InfoQ一直在关注 Node.js的发展,在今年的两次Qcon大会(北京站和杭州站)都有专门的讲座。为了更好地促进Node.js在国内的技术推广,我们决定开设“深入 浅出Node.js”专栏,邀请来自Node.js领域的布道师、开发人员、技术专家来讲述Node.js的各方面内容,让读者对Node.js有更深入 的了解,并且能够积极投入到新技术的讨论和实践中。
相关厂商内容

Flash Builder 4.5高级版试用版免费高速下载

专栏的第一篇文章《什么是Node.js》尝试从各个角度来阐述Node.js的基本概念、发展历史、优势等,对该领域不熟悉的开发人员可以通过本文了解Node.js的一些基础知识。
从名字说起

有关Node.js的技术报道越来越多,Node.js的写法也是五花八门,有写成NodeJS的,有写成Nodejs的,到底哪一种写法最标准呢,我们不妨遵循官方的说法。在Node.js的官方网站上, 一直将其项目称之为”Node“或者”Node.js“,没有发现其他的说法,”Node“用的最多,考虑到Node这个单词的意思和用途太广泛,容易让 开发人员误解,我们采用了第二种称呼——”Node.js“,js的后缀点出了Node项目的本意,其他的名称五花八门,没有确切的出处,我们不推荐使 用。
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来解释执行,简单的命令类似于:

#node helloworld.js

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

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");

上面的代码搭建了一个简单的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。关于动态语言和静态语言的优缺点比较在这里不再展开讨论。只说三点:

    Javascript作为前端工程师的主力语言,在技术社区中有相当的号召力。而且,随着Web技术的不断发展,特别是前端的重要性增 加,不少前端工程师开始试水”后台应用“,在许多采用Node.js的企业中,工程师都表示因为习惯了Javascript,所以选择Node.js。
    Javascript的匿名函数和闭包特性非常适合事件驱动、异步编程,从helloworld例子中我们可以看到回调函数采用了匿名函数的形式来实现,很方便。闭包的作用则更大,看下面的代码示例:

    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    }); });

    在上面的代码中,我们需要在end事件中处理responseHTML变量,由于Javascript的闭包特性,我们可以在两个回调函数之外定义responseHTML变量,然后在data事件对应的回调函数中不断修改其值,并最终在end事件中访问处理。
    Javascript在动态语言中性能较好,有开发人员对Javacript、Python、Ruby等动态语言做了性能分析,发现Javascript的性能要好于其他语言,再加上V8引擎也是同类的佼佼者,所以Node.js的性能也受益其中。

Node.js发展简史

2009年2月,Ryan Dahl在博客上宣布准备基于V8创建一个轻量级的Web服务器并提供一套库。

2009年5月,Ryan Dahl在GitHub上发布了最初版本的部分Node.js包,随后几个月里,有人开始使用Node.js开发应用。

2009年11月和2010年4月,两届JSConf大会都安排了Node.js的讲座。

2010年年底,Node.js获得云计算服务商Joyent资助,创始人Ryan Dahl加入Joyent全职负责Node.js的发展。

2011年7月,Node.js在微软的支持下发布Windows版本。
Node.js应用案例

虽然Node.js诞生刚刚两年多,但是其发展势头逐渐赶超Ruby/Rails,我们在这里列举了部分企业应用Node.js的案例,听听来自客户的声音。

在社交网站LinkedIn最新发布的移动应用中,NodeJS是该移动应用的后台基础。LinkedIn移动开发主管Kiran Prasad对媒体表示,其整个移动软件平台都由NodeJS构建而成:

    LinkedIn内部使用了大量的技术,但是在移动服务器这一块,我们完全基于Node。

    (使用它的原因)第一,是因为其灵活性。第二,如果你了解Node,就会发现它最擅长的事情是与其他服务通信。移动应用必须与我们的平台API和数 据库交互。我们没有做太多数据分析。相比之前采用的Ruby on Rails技术,开发团队发现Node在性能方面提高很多。他们在每台物理机上跑了15个虚拟服务器(15个实例),其中4个实例即可处理双倍流量。容量 评估基于负载测试的结果。

企业社会化服务网站Yammer则利用Node创建了针对其自身平台的跨域代理服务器,第三方的开发人员可以通过该服务器实现从自身域托管的 Javascript代码与Yammer平台API的AJAX通信。Yammer平台技术主管Jim Patterson对Node的优点和缺点提出了自己的看法:

    (优点)因为Node是基于事件驱动和无阻塞的,所以非常适合处理并发请求,因此构建在Node上的代理服务器相比其他技术实现(如Ruby)的服 务器表现要好得多。此外,与Node代理服务器交互的客户端代码是由javascript语言编写的,因此客户端和服务器端都用同一种语言编写,这是非常 美妙的事情。

    (缺点)Node是一个相对新的开源项目,所以不太稳定,它总是一直在变,而且缺少足够多的第三方库支持。看起来,就像是Ruby/Rails当年的样子。

知名项目托管网站GitHub也尝试了Node应用。该Node应用称为NodeLoad,是一个存档下载服务器(每当你下载某个存储分支的 tarball或者zip文件时就会用到它)。GitHub之前的存档下载服务器采用Ruby编写。在旧系统中,下载存档的请求会创建一个Resque任 务。该任务实际上在存档服务器上运行一个git archive命令,从某个文件服务器中取出数据。然后,初始的请求分配给你一个小型Ruby Sinatra应用等待该任务。它其实只是在检查memcache flag是否存在,然后再重定向到最终的下载地址上。旧系统运行大约3个Sinatra实例和3个Resque worker。GitHub的开发人员觉得这是Node应用的好机会。Node基于事件驱动,相比Ruby的阻塞模型,Node能够更好地处理git存 档。在编写新下载服务器过程中,开发人员觉得Node非常适合该功能,此外,他们还里利用了Node库socket.io来监控下载状态。

不仅在国外,Node的优点也同样吸引了国内开发人员的注意,淘宝就实际应用了Node技术:

    MyFOX 是一个数据处理中间件,负责从一个MySQL集群中提取数据、计算并输出统计结果。用户提交一段SQL语句,MyFOX根据该SQL命令的语义,生成各个 数据库分片所需要执行的查询语句,并发送至各个分片,再将结果进行汇总和计算。 MyFOX的特点是CPU密集,无文件IO,并只处理只读数据。起初MyFOX使用PHP编写,但遇到许多问题。例如PHP是单线程的,MySQL又需要 阻塞查询,因此很难并发请求数据,后来的解决方案是使用nginx和dirzzle,并基于HTTP协议实现接口,并通过curl_multi_get命 令进行请求。不过MyFOX项目组最终还是决定使用Node.js来实现MyFOX。

    选择Node.js有许多方面的原因,比如考虑了兴趣及社区发展,同时也希望可以提高并发能力,榨干CPU。例如,频繁地打开和关闭连接会让大量端 口处于等待状态,当并发数量上去之后,时常会因为端口不够用(处于TIME_WAIT状态)而导致连接失败。之前往往是通过修改系统设置来减少等待时间以 绕开这个错误,然而使用连接池便可以很好地解决这个问题。此外,以前MyFOX会在某些缓存失效的情况下出现十分密集的访问压力,使用 Node.js便可以共享查询状态,让某些请求“等待片刻”,以便系统重新填充缓存内容。

小结

本文简要介绍了Node.js的基本知识,包括概念、特点、历史、案例等等。作为一个仅仅2岁的平台,Node.js的发展势头有目共睹,越来越多的企业开始关注并尝试Node.js,前后端开发人员应该了解相关的内容。
参考文献

[1] http://nodejs.org/

[2] http://beakkon.com/geek/node.js/why-node.js-single-thread-event-loop-javascript

[3] http://www.tbdata.org/archives/1285

[4] http://www.infoq.com/interviews/node-ryan-dahl

[5] http://www.infoq.com/cn/news/2011/08/enterprise-nodejs

[6] http://www.infoq.com/cn/news/2010/11/nodejs-joyent

[7] http://www.infoq.com/cn/news/2011/06/node-exe

[8] http://nodenode.com/post/1176414531/node-js-a-short-history

[9] http://www.infoq.com/cn/news/2011/05/nodeparty-hangzhou

from 深入浅出Node.js(一):什么是Node.js
http://www.infoq.com/cn/articles/what-is-nodejs

深入浅出Node.js(二):Node.js&NPM的安装与配置
http://www.infoq.com/cn/articles/nodejs-npm-install-config
----------------------------------------------------------------------

让node.js充分利用多核服务器的性能

 看了一些node.js的相关文档,node.js除了拥有非阻塞I/O,快速开发等诸多优点外,其缺点也很明显:

1、单进程,只支持单核CPU,不能充分的利用多核CPU服务器。

2、单进程,一旦这个进程崩掉,那么整个web服务就崩掉了。(当然这个可以通过代码的健壮性来弥补)

开发环境:

vMware Red Hat 虚拟机

CPU: 两个

内存:1GB

以下有说的不对的地方还请多多指教:

近期我借助node.js开发了一套网络聊天室的应用,简单实现了大厅聊天,私聊,群聊。用户信息、群组成员信息和聊天内容等借用redis进行存储。

具体聊天室的功能请参阅我的博客:http://snoopyxdy.blog.163.com/blog/static/60117440201172510426330/

现在想要1个CPU绑定一个node.js聊天室进程,需要借助这个命令:taskset,(具体请参阅相关文档)

在linux下运行: node server.js 和 node server0.js

我设定server.js监听端口为8888,server0.js监听端口为8889.

OK,现在2个node.js聊天室程序启动起来了。但是要注意,这2个聊天室不能相互聊天,但是用户表是一个,就当他是两个不同的房间把。当然聊天室代码是一套,只是拷贝了一份server.js,在里面监听不同的端口就可以了。

然后我们要对两个node.js的进程绑定不同的CPU:(目前我的虚拟机就2个CPU)

ps -ef | grep node  (用来查看node的进程号)

root   19277  2517 15 10:12 pts/1    00:00:20 node /usr/local/node/src/chating_express/server.js

root     19283 2566 18 10:13 pts/2    00:00:20 node /usr/local/node/src/chating_express/server0.js

root     19291  2384  0 10:15 pts/0    00:00:00 grep node

找到了上述2个PID后,就利用taskset来进行CPU绑定,命令如下:(这里只写其中一个)

taskset -pc 0 19277  (其中0代表CPU0,以此类推)

pid 19277's current affinity list: 0,1

pid 19277's new affinity list: 0

出现上述信息代表绑定成功,同样方法绑定另外一个。

绑定结束后我们怎么知道他们运行在不同的CPU上呢?找另外一台虚拟机,装好webbench,分别对两个node.js聊天室端口进行压力测试,然后用top命令查看各个CPU的使用情况,(小窍门,输入TOP 命令,然后按数字“1”,在压力测试时不停的按“ENTER”),结果表明在压力测试server.js端口时CPU0使用率长期保持在60%以上而 CPU1则使用率长期在20%以下,而测试server0.js端口时,CPU1长期保持在60%,而CPU0则保持在20%以下,结果表明绑定成功!(之前曾经乌龙了一次,用自己的虚拟机webbench压力测试,结果发现2个CPU使用率都很高,一直检查不出问题,后来发现webbench也要使用CPU的,泪奔!!!)

然后安装nginx,对nginx进行配置,只需在配置文件中加入:(具体nginx的安装和设置请参阅相关文档)

upstream node_server_pool {

server 10.1.1.202:8888 max_fails=1;

server 10.1.1.202:8889 max_fails=1;

}

server

{

listen       80;

server_name 10.1.1.202;

location /

{

proxy_pass http://node_server_pool;

proxy_set_header Host 10.1.1.202:80;

proxy_set_header X-Forwarded-For $remote_addr;

}

然后软重启nginx服务,访问10.1.1.202这个地址80端口,就可以让nginx做反向代理了和负载均衡了。

注:上述的这些配置需要node.js服务端代码良好的支持,session的存储建议使用redis,这样可以共享session否则需要用到ip_hash了(具体nginx优化配置请参阅相关文档),在多核CPU上跑多个node.js进程他们之间的内存是不共享的,这点需要牢记啊。
由于之前曾经压力测试过node.js的空框架,发现其内存消耗并不是很高,所以理论上来说,如果4核cpu且内存8G以上,开4个node.js进程跑在一个服务器上,用nginx做负载均衡,其性能和稳定性都应该比单个node.js提升很大.
---------------------------------
 https://github.com/LearnBoost/socket.io
https://github.com/learnboost/socket.io/wiki