http://blog.csdn.net/heiyeshuwu/article/details/6582558
作者:zhangxin
来源:http://blog.csdn.net/zhangxin09/article/details/5836777
Node.JS是资深C程序员Ryan Dahl(http://four.livejournal.com/)的作品,依据Google著名的开源JavaScript引擎V8来进行二次开发的Web I/O服务器(http://nodejs.org/)。V8本身是非常快的JavaScript引擎,处理JS执行运行的速度非常高。相关测试表明,FireFox、Opera和IE的JS引擎速度都不及V8来得快。而且,还可以说,只要浏览器之间的JS引擎大战一日不减,NodeJs就可以从中受益。有竞争才有进步:)。
NodeJS基本用法也是十分简单明了的,我们看看一下这一句,就是最简单的代码:
- var
- sys = require('sys')
- ,http = require('http');
- http.createServer(function (req, res) {
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.write('Hello World');
- res.close();
- }).listen(8006);
- sys.puts('Server running at http://127.0.0.1:8006/');
- http.createServer(function (req, res) {
- req.addListener("end",function(){
- sys.puts("request end");
- });
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.write('Hello World');
- res.close();
- }).listen(8006);
从宏观层面看以上的演示,上面仅仅是一个比较底层的操作,都是低层次的控制,不足以提供更多High Level逻辑。要增强NodeJS这个平台功能,我们可以使用其他围绕NodeJS而开发模块,甚至是一个初具规模的“框架”,——其中一个就是Express(http://github.com/visionmedia/express)。安装Express的前提是安装另一个依赖包:Kiwi(http://github.com/visionmedia/kiwi),然后键入kiwi -v install express才可以正式安装Express。安装好Express之后,立刻输入下面代码测试一下:
- var sys = require("sys"),
- kiwi = require("kiwi"),
- express = kiwi.require('express');
- get('/', function(){
- this.redirect('/hello/world')
- });
- get('/hello/world', function(){
- return 'Hello World'
- });
- get('/goodbye/world', function(){
- return 'Goodbye World'
- });
- run();
Express运行的URL和端口是http://localhost:3000,默认请求的规则都会被转移到/hello/world目录,返回'Hello World'的字符串。访问/goodbye/world目录就返回'Goodbye World'。Express之所以被称为一个“框架”,应该还有其他的功能在内,例如请求路由、渲染视图的其他高级功能。
Web服务器的事件编程其意义
我们知道并且十分清楚,JavaScript是单线程的(就当前流行的js v1.5而言),怎么避免I/O通道上的阻塞(block)呢?——基于事件驱动(Event-based)的编程或者或者是一种可行之道,为不少后台程序解决线程/阻塞之用。自从Viusal Basic流行起,基于事件驱动的编程模型在GUI之上早已为我们所熟悉,存在很长的一段时间了。若说上早期的开发,那时我们经常的一个做法,就是在main()方法写一个无限循环(while(true) loop),从而获得控制任何时刻程序的能力,亦算是一种朴素的“事件”模型。我们清楚,JavaScript天然是Function First Level的,对一个函数还可以送入一个函数的参数(即传入一个闭包closure),换言之,JS天生与事件驱动的编程是不谋而合,相得益彰的。鉴于此,Node.js的事件编程理念的确是一个亮点。
另外一点,高并发的Web服务器一直乃久经不衰的热点和话题,也许有很多的解决方案,性能达到一定的指标也没有问题,不过问题是,因而,在保证并满足性能达标的前提下,却很少见一个自然、适合编写业务逻辑的开发平台,以JavaScript为开发语言的NodeJS正好符合这一需求,一种符合大多数人需求的DSL(估计js玩家数量可观得可以……)。例如,一个简单的计数器设计,我登录某个url,就触发一个计数器的事件,十分自然。当然,更重要的是,还是JS的语言特征,即Function、闭包、相似的C语法、精炼与简洁的风格等等,尤其Function,简直为事件模型而生,轻松吻合node.js异步机制的理念, 才是真正吸引玩家去乐此不疲的原因。
尽管NodeJS现在还非常的年轻,也没有久经世故,不过值得可喜的是,现在涌现了一大批围绕NodeJS的插件、增强包,有的是链接数据库,有的是用于logging、Template模板、单元测试,有的还是服务于长链接下(long polling)轮询的……等等。我们可以参见NodeJS具体的模块列表,在http://wiki.github.com/ry/node/modules。
分享一个Node.js的专题网站,适合初学者:
nodejs与websocket的资源:
http://blog.johngoulah.com/2010/03/nodejs-websockets-and-the-twitter-gardenhose/http://blog.andregoncalves.com/2009/12/29/Nodejs-twitter-streaming-with- html5-websockets.html
写node.js的插件(C++)
https://www.cloudkick.com/blog/2010/aug/23/writing-nodejs-native-extensions/
在win平台上跑node.js,借助虚拟机:
http://www.lazycoder.com/weblog/2010/03/18/getting-started-with-node-js-on-windows/
在win直接运行的可执行文件(已编译):
http://www.grati.org/?page_id=213 不错的node.js中文资源
新型的服务端正在进入我们的视野,让我们投入了关注的目光,例如近来的NodeJS 算比较抢眼的一员。
之所以创造NodeJS ,引用原作者Ryan 语,目标就是为了可以更轻松地编写具有可伸缩性的网络程序。咋一看,这样的目标作为网络开发人员们何曾不想拥有。——于是看看Nodejs 是怎么实现的。首先由浅入深说下简单的概念:无论是复杂的业务逻辑,还是简单的“Hello World” 也罢,客户端发送链接过来,Web 服务器肯定要一一全单照收,不会拒“链接”于千里之外。当中所说的性能指标就是我们日常会提到的——“并发(Concurrency )”。Web服务器是并发处理这些链接请求的。并发越高,服务器性能越好——到最终,大概是要解决著名C10K 问题。在处理并发的这个技术问题上,NodeJS 表现出来的,就是高并发、低消耗的佼佼者。
NodeJS 有一定性能优势,却引发了我们技术人员的浓厚兴趣。不免要问,NodeJS 是如何办到的?NodeJS 是开源项目,如果不打算直接通过源码了解,我们还是可以通过网上一点资讯了解。笔者收集了关于NodeJS 的几遍文章、博客,略有心得,将它们想表达NodeJS 的特点、缺点、相关原理、前期分析、选型等等各方的问题“共冶一炉”,说出个NodeJS 初步分析的大概。
如果各位看官不太了解服务端的运作的话,我们稍微回顾一下请求Request这一环节的过程。现今多数的Web 服务器中,有一条新的链接就会申请一条线程来负责处理至到这个Request 周期结束,接着执行其他流程。可以想象,成千上万个链接便有成千上万条线程(Thread-spawning )。每条线程姑且以堆栈2MB 的消耗去计算,一条条线程它们的累加都是不小的数目。如何优化和改进本身就是一个大问题,此外,使用系统线程,必须考虑线程锁的问题,否则造成堵塞主进程又是一个令人操心的难题。NodeJS 则通过基于事件的异步模型绕开了基于线程模型的所带来的问题。NodeJS 使用JavaScript 单线程(Single-threaded )轮询事件,设计上比较简单,高并发时,不仅根本性的减少了线程创建和切换的开销(因而没有吓人的消耗),而且由于没有锁,也不会造成进程阻塞。每当有链接发起到服务端之后,NodeJS 会透过epoll 、 kqueue 、/dev/poll 或 select 指令通知操作系统,有新链接到达,应执行指定的回调函数(Callback )。每个链接从成本上说只消耗一个堆(heap allocation )。
单线程的Nodejs ?
NodeJS 使用单线程就足以提供高速的并发能力?是的,实际上著名nginx 也是基于单线程的。然而拜C++ 所赐,Node.js 却拥有多线程的运行环境。NodeJS 虽带有JS 的名称,以JS 为卖点,——确实也如此,轻盈的JS 替C/C++跟开发人员打交道,但必须强调,JS 终究是编写中间件的脚本语言,底层发挥作用的仍然是C/C++ 。为了实现这些设计目标,Node.js 使用了Google V8 并打包了其中的一些库:
-
libev 实现了时间循环并封装了底层使用的具体的技术(如select, epoll 等)。
-
作者自己写的http-parser 等协议和其他等等。
其中libev 正是实现多线程NodeJs 的基础(edit on 2010-9-12:Are you sure to say so???有什么证据??)。JavaScript 仍旧发挥脚本语言的本色,一方面将C/C++ 的复杂性屏蔽,一方面向程序员呈现优雅的API。Node.js 在适合一些较轻松的场合,包括一些分离器Dispatcher 、Request 、BeansTalk 、AMQP 消息应该没有问题。但依据国外一些博客文章分析就是,实际生产中可能会意外频频,发生一个错误就会挂起node.js ,所以单线程不太可靠或许是nodejs 一个先天的缺点。另外,编写NodeJS 的扩展仍需要出来高深的C++ ,恐怕须完善好C与JS 之间的接口层,编写NodeJS 扩展才是我辈能力范围内。写本文的时候,Node 属新生事物,无须讳言,笔者没有太多的直观经验。究竟实际上有多少的情景允许我们一边计算,一边做其他的事情而稳定无虞的呢?希望可以有待更多的观察。
上述的几点,的确提到了“基于线程模型”v.s “ 基于事件模型”之争,目的就在于,除了明晰分辨它们的利弊之外,还不能不回答这样一个问题:既然“基于线程模型”消耗得那么厉害,那么为什么现在这么多的Apaches 、IIS 都运行得好好的?
基于事件的Web 服务器相对是比较新的概念,可以做到比较好的性能,因而受到推崇一点不意外,像Node.js 那样的,——而传统的基于线程的模型服务器成熟程度高,况且仍不断地发展,例如Apache 的PHP 会派生出很多的OS 线程来解决并发的问题,若一个请求挂起了其所在的线程,可以保证其他的线程也不会受到影响,不会冻结整个服务器进程,显得也比较合理。必须指出的是,像对于如何处理并发来选择“基于线程模型”v.s “ 基于事件模型”这样的讨论,业界一直存在,并不是说基于事件模型的一定优秀无敌,尚有许多一一斟酌讨论的地方,具体如何就不一一展开了。
那么,Node.js 的优势到底在哪?
应对长链接的压力
例如,某网站pv 非常可观,与用户互动频繁,那么它的线路总是处于高峰,自然它的网络进进出出肯定非常频繁,势必要求后台要赶快处理好前一个请求,以便接着有时间来处理一个请求,越快越好、越高效。好在,我们的请求大小都不是很大,通常几十字节(如http://domain:80 ,一个GET 操作,cookies 不大的话),控制线程在一个很小的单位,如此往返一个来回很快搞掂。那当然属于I/O 最简单的情况了,稍为复杂的一些就是POST 表单、文件上传等的任务。但好在不是每个链接皆如此,服务器还可以吃得消久一点的链接。可是,这时候,来问题了——
话说Web2.0 时兴的元素,Web IM 、Web GAME 、Web 协作……无一不需求长链接为其服务的。长链接,或长轮询,是企图突破现有HTTP v1.1 链接模型,把无态(Stateless )的点对点链接变为人们理想的有态(Stateful ),也就是Request/Response 互不分离,总是在线有沟通着。实际情形HTTP 并没有提供这种的API 或者说服务。当前我们大抵采用折衷的方法:打开一HTML 页面立刻发送服务端的AJAX 请求,就算是没有内容的请求都好,没有关系,服务器就千万别像普通AJAX 那样接收请求,处理流程后就返回Repsonse ,不要立刻返回内容而是等待,换言之,就是保持链接。只是在有消息发出的时候才返回Response 然后浏览器渲染Response 内容。例如,有好友发悄悄话给你,通过服务器发送到你浏览器上显示,然后立刻发起新的请求,让彼此之间的链接一直保持下去。
介绍前面的这么多,无非想说明,客户端与服务端一旦链接后,除非用户关闭浏览器,否则是不会断开keep -alive链接的。这样,对于同时维系着数十条或者数百条(聊天室)的connection 的服务器,一直非空闲,还要顾上各方面资源(CPU usage 、consuming memory…… ),显然不是一件容易事情,甚至如项目“开心网”那样成千上万笔connection 场景就是对服务端极大的考验,如果占用的线程不能得到迅速释放,将会给服务器带来灾难性的后果!
于是一些Web Serever 认真考虑到这点,在新版中提供适应长链接的场景,例如Jetty 很早的时候就提供一个J2EE容器的解决方案,与Comet 的通讯协议对接上。每个Server 的架构不一,然而如何改进和改进目标都有参考意义,但改进已是必然了,就要重新考虑Web I/O ,提供足够快而稳定性能适应长链接的场景。明显,不得不重新考虑服务端的设计了,然而,背后要考虑的事情就多了。总之,可以想象任务艰巨性,不仅要考虑前方I/O 高并发,低响应时间的请求,还要考虑整套的服务供应者怎么去资源调控,具体如负载平衡(Load Balancing )、动态DNS 切换、DB 的集群、多个文件镜像的问题,往往配合起来就有许多不可预料的问题发生。一个环节有问题真个系统的堵塞了。这一启承转合要处理好。
不是有WebSocket 标准吗?HTML5 的世界尽管在移动平台上很热闹,普通浏览器升级却觉得是另一回事。如果现在一下子都是支持WebSocket 的浏览器,那不用说准是皆大欢喜了,但事实和将来的预测表明Web Socket 完全是另外一回事,咱和咱用户面对的仍旧那些僵硬不化的IE6…… 所以说在WebSocket 不现实的今天,将善于“长链接”的Nodejs 派上用场便有很充分的理由。
p.s :……包括用flash socket 组件那些hack 的都不算。
发挥事件模型的威力
Node.Js 带来了一股清新之风,与其使用JS 乃是密不可分的。这次,神奇的JavaScript 又一次成为了胶水语言,为“基于事件驱动模型(Evnent-based )”开发埋下重要的伏笔。事件本质上一个时空不一致的非线性模型,或所谓的“异步(Asynchronization )”。事件发生的顺序按照外界对其发出的时刻而确定,有的在先,有的在后,有时也可以齐头并进,一起同时触发,——结束时也可以快的快、慢的慢。(呵呵,本人有些无聊,突然想起小学课本,华罗庚那篇的《统筹方法 》“……想泡壶茶喝。当时的情况是:开水没有。开水壶要洗,茶壶茶杯要洗;火已升了,茶叶也有了。怎么办?……”,实有异曲同工之妙!)。具体说,就是在一方面处理诸如数据库查询/ 存储、磁盘读写、网络延时那一类费时的任务,一方面处理内存中高速的运作,来作一个合理地平衡调度。当然,回归这一点的要求与多线性模型的I/O 要求是无异的。总之不是直接的某个函数method() 去执行(那是同步的方式,Node.js 也支持),而是写回调callback ;如果换了是同步方式,就必须等待上一个任务结束,才能开始下一个任务。本来可以齐头并进的机会却白白浪费掉了。换言之,大多数操作往往是I/O 的等待,不过NodeJS 底层对于JavaScript 该层面来说,由后台线程调用JavaScript 函数,因此无碍JS 代码的执行,实现异步的操作,即“非阻塞”。例如下面摘自文档的一个例子:
- var
- posix = require("posix")
- ,sys = require("sys");
- var promise = posix.unlink("/tmp/hello");promise.addCallback(function () {
- sys.puts("successfully deleted /tmp/hello");
- });
如果删除文件成功,触发success 事件执行addCallback() 所定义的回调函数;即是删除文件失败,产生wait 的信号,直至timeout 的时限,也不会阻塞其他JS 代码的执行。在Node.js 的API 中,到处使用事件的概念,包括许多方法都设有“同步”和“异步”的两种方式供选择,故所以我们不用担心写的代码会阻塞Node.js 的I/O 。
个人认为,从感觉而言,两者之间还有一点的差异可能是,多线性模型不像编写事件那么自然。定义事件起来隐约会有一种写“DSL” 的感觉,尤其在JS 这个Function First 的脚本帮助下。另外可以参考一下前一篇《node.js引言 》的博文 ,此处不再复述。
题外话:貌似AJAX AIR in Js 呈现了也是一种异步调用方式(记得SQLquery 时语法相似)。
事件循环的console模拟图
实际上,NodeJs 不是第一家标榜事件的Web Server ,早在NodeJs 之前,在各种语言中都有事件的实现,不能不提的就是nginx 。不过使用JavaScript 还属于头一遭吧。过去几年可以说是JS 引擎发展的高峰期,就连最保守的微软也要IE9 把落后的JS 解释速度争回来,亲爱的服务端方面却又怎么按耐的住呢?自然,革新速度后,JS VM 引入到Server side 的工作便是一个顺理成章的事。
话说回来基于事件理念的Server 。Node.js 最初得益于Ruby 的Event Machine 和Python 的Twisted ,将包括各种I/O操作定义在回调函数中,通过事件不断轮询任务列表来触发那些Callback ,——并且NodeJS 有创新的地方,就是提出新的思路来呈现事件机制。从原理上讲,Node.js 不仅仅是一个库,而是尝试利用语言机制来构建的事件模型。Event Machine 或Twisted 不是这样,它们都是在代码开始和结束的时候插入回调函数来完成一个阻塞的调用,然后这个过程的启用,就用:
- EventMachine::run()
而Node.js 没有这种代码顺序的限制,可以在定义代码之后再插入新的代码,继续参与事件。同时node.js 也不会Twisted Python 那样提供“延时线程(defer to thread )”,实际是堵塞代码的“陷阱”。
尽管我们这里说的事件模型好像比较简单,但是许多的基础设施对异步操作的支持的不足的,尤其普通用户根本不会自己去创建业务事件。相关内容在介绍NodeJS 的Slide 有介绍(搜索jsconf.pdf ),说明为什么Nodejs 出现之前没有类似NodeJS 的“物体”出现,同时也说明设计nodejs 要克服的难关。
结语
最后一点,谈谈node.js 为什么选择Google V8 的JS 引擎而不是另一个著名的SpiderMonkey 引擎。抛开速度等的硬性指标不表,可能是SpiderMonkey 源码仍然比较复杂的缘故,不好把玩,人们自然就青睐于虽然是C++ 的V8了。
本文介绍了一位JS 爱好者对NodeJS 以及后台初步感性的了解,没有深刻的认识,竟也成文,看官们可作取舍(trade-off ),将就来读,并请积极献言,一同讨论。
参考:
Werner Schuster ,http://www.infoq.com/news/2009/11/nodejs-evented-io
Louis Simoneau ,Node.js is the New Black
UDP & Dgram UNIX daemon Socket supporthttp://groups.google.com/group/nodejs/browse_thread/thread/665422a1dc28d874
Paul Querna, Drinking the Node.js Kool-Aid