被误解的 Node.js

做者:王 群锋, 软件工程师, IBMhtml

出处:http://www.ibm.com/developerworks/cn/web/1201_wangqf_nodejs/java


被误解的 Node.js

Node.js 被设计用来开发大规模高并发的网络应用,这种网络应用的瓶颈之一是在 I/O 的处理效率上。因为硬件及网络的限制,I/O 的速度每每是固定的,如何在此前提下尽量处理更多的客户请求,提升 CPU 使用效率,便成了开发人员面临的最大问题。得益于基于事件驱动的编程模型,Node.js 使用单一的 Event loop 线程处理客户请求,将 I/O 操做分派至各异步处理模块,既解决了单线程模式下 I/O 阻塞的问题,又避免了多线程模式下资源分配及抢占的问题。至于使用 JavaScript 开发服务器端代码,这并非什么新鲜事物,JavaScript 原本就是一种完备的编程语言,微软的 IIS 服务器很早就支持 JavaScript 在其中运行。本文将重点讲述 Node.js 基于事件的编程模型,并与传统的处理方式进行对比,帮助您更好的理解 Node.js。node

网络应用的性能瓶颈程序员

网络应用的性能瓶颈之一在于 I/O 处理上,下表来自 Node.js 的做者 Ryan Dahl 为 JSConf 大会所做的 讲演,对比了在不一样介质上进行 I/O 操做所花费的 CPU 时间。您可以清楚的发现,访问磁盘及网络数据所花费的 CPU 时间是访问内存时的数十万倍,而如今的网络应用,却须要大量的访问磁盘及网络,好比数据库查询、访问互联网等。如何提升此时 CPU 的利用效率,便成了提高网络应用性能的关键。web


表 1. 不一样介质下 I/O 操做花费对比
I/O CPU Cycle
L1-cache 3
L2-cache 14
RAM 250
Disk 41000000
Network 240000000

传统的处理方式数据库

单线程express


清单 1. 单线程下的阻塞式 I/O
				 
 var result = 
 db.query("select * from T"); 
 // 使用该查询结果

上述代码描述了一个常见的案例,客户端发起一个 I/O 请求,而后等待服务器端返回 I/O 结果,结果返回后再对其进行操做,但这种请求经常须要很长时间(对于服务器的 CPU 处理能力来讲)。这一过程当中,服务器没法接受新的请求,即阻塞式 I/O。这种处理方式虽然简单,却不实用,尤为是面对大量请求的时候,简直就不可用。这种情景相似在火车站售票窗口排队买票,若是您在春节期间去北京火车站排队买过票,毫不会认为这是一种好的处理方式。庆幸的是,如今不多有服务器采起这种处理方式。编程

多线程服务器


清单 2. 多线程下的阻塞式 I/O
				 
 var result = 
 db.query("select * from T"); 
 // 使用该查询结果

该方式下,服务器为每一个请求分配一个线程,全部任务均在该线程内执行,就像火车站多开了几个卖票窗口,处理效率高了许多。但就如读者看到的那样,在春节期间各个售票窗口前仍是人满为患,为何火车站再也不多开一些售票窗口呢?固然是由于成本。线程也同样,服务器每建立一个线程,每一个线程大概会占用 2M 的系统内存,并且线程之间的切换也会下降服务器的处理效率,基于成本的考虑,这种处理方式也有必定的局限性。然而,这却不是最主要的,主要的是开发多线程程序很是困难,容易出错。程序员需考虑死锁,数据不一致等问题,多线程的程序极难调试和测试。基本上在程序运行出错的时候,程序员才知道本身的程序有错误。而这种错误的代价每每又是巨大的,那些访问量巨大的电子商务网站时常会曝出价格错误等致使公司损失的新闻。

事件驱动


清单 3. 基于事件驱动的编程模型
				 
 db.query("select..", function (result) { 
 // 使用该查询结果
 }); 
 // 继续干其余的事
 // ……

上述代码的好处是:使用一个线程执行,客户发起 I/O 请求的同时传入一个函数,该函数会在 I/O 结果返回后被自动调用,并且该请求不会阻塞后续操做。就像电话订票,设想你一大早来到办公室,给火车站打个电话,将本身的票务信息,地址告诉对方,而后放下电话,泡杯茶,浏览一下网页,回复一下今天的电子邮件,你彻底不用管火车票的事了,若是订到票,火车站会派快递公司按你电话中提到的联系方式送票给你。无疑,这是一种极其理想的处理方式。

下图说明了这种编程模型,全部请求以及同时传入的回调函数均发送至同一线程,该线程一般叫作 Event loop 线程,该线程负责在 I/O 执行完毕后,将结果返回给回调函数。这里要注意的是 I/O 操做自己并不在该线程内执行,因此不会阻塞后续请求。


图 1. Event loop
图 1. Event loop  

Node.js 简介

有了上面对于事件处理编程模型的介绍,Node.js 就很好理解了。Node.js 是采用事件处理编程模型的 JavaScript 平台,它容许程序员开发大规模高并发的网络应用。这个概念并不新鲜,在 Node.js 以前,不少语言都提供了相似的平台:Python 的 Twisted,Perl 的 AnyEvent,Ruby 的 EventMachine。Node.js 优于其余平台的另外一个好处是全部的 I/O 操做都以异步方式实现,让程序员将主要精力放在应用的业务逻辑上。

为何选用 JavaScript

事实上,在实现 Node.js 之初,做者 Ryan Dahl 并无选择 JavaScript,他尝试过 C、Lua,皆因其欠缺一些高级语言的特性,如闭包、函数式编程,导致程序复杂,难以维护。而 JavaScript 则是支持函数式编程范型的语言,很好地契合了 Node.js 基于事件驱动的编程模型。加之 Google 提供的 V8 引擎,使 JavaScript 语言的执行速度大大提升。最终呈如今咱们面前的就成了 Node.js,而不是 Node.c,Node.lua 或其余语言的实现。

一个例子

本文将在这里使用 Node.js 实现一个小型的 Web 应用,它将随机为用户显示一条谚语或名人名言,并容许浏览者添加本身喜欢的谚语。用 Node.js 开发 Web 应用很是简单,下面这段一百多行的代码就实现了一个完整的应用。若是您还没有安装好 Node.js,请登陆其官方网站查看详细安装说明。


清单 4. 导入所须要的模块(proverbs.js)
				 
 // 导入所需模块
 var http = require("http"); 
 var url = require("url"); 
 var qs = require('querystring'); 

首先须要导入该应用所须要的模块,其中 http 模块负责建立 Web 服务器及 HTTP 相关服务,url 模块负责解析 URL 地址,querystring 模块负责处理请求参数。


清单 5. 数据存储(proverbs.js)
				 
 // 这里为了方便使用了全局变量
 var proverbs = [ 
		"The turtle wins the race.", 
		"God hides in the details.", 
        "There are two ways to write error-free programs; only the third one works.", 
        "Perfect practice makes perfect."
 ]; 

这里为了方便,使用全局变量 proverbs存储已有谚语,在正式的应用中,应该考虑使用文件或数据库存储。


清单 6. 建立 Web 服务器(proverbs.js)
				 
 // 建立一个 Web 服务器
 http.createServer(onRequest).listen(8888); 
 console.log("server is running..."); 

使用 Node.js 开发 Web 应用很是简单,甚至不用配置 Web 服务器,一行代码就建立成功一个 Web 服务器,同时传入一个回调函数,服务器建立成功后,代码并无阻塞到那里,而是接着往下执行,这就是事件驱动模型的编程风格,在 Node.js 里将会大量采用这种方式。


清单 7. 请求处理函数(proverbs.js)
				 
 // 请求处理函数
 function onRequest(request, response) { 
	 var pathname = url.parse(request.url).pathname; 
	 console.log("Reqeust for " + pathname + " received."); 

 if (pathname === "/" || pathname === "/index" || pathname === "/proverb") { 
		 getProverb(response); 
	 } else if (pathname === "/add") { 

		 if (request.method.toLowerCase() == 'post') { 
			 var body = ''; 
			 request.on('data', function(data) { 
				 body += data; 
			 }); 

			 request.on('end', function() { 

				 var POST = qs.parse(body); 
				 add(POST.text, response); 

			 }); 
		 } else { 
			 addProverb(response); 
		 } 

	 } else { 
		 response.writeHead(404, { 
			"Content-Type" : "text/plain"
		 }); 
		 response.write("404 Not found"); 
		 response.end(); 
	 } 

 } 

该函数负责分发请求,将接收到的 URL 根据规则转发至对应的请求处理模块。


清单 8. GET 请求(proverbs.js)
				 
 function getProverb(response) { 
	 var body = '<html>'
			 + '<head>'
			 + '<meta http-equiv="Content-Type" content="text/html; '
			 + 'charset=UTF-8" />'
			 + '</head>'
             + '<body style="font-size: 4em;line-height: 1.2; margin-top: 200;">'
             + '<blockquote>'+ proverbs[Math.floor(Math.random()* proverbs.length)]
                + '</blockquote>' + '</body>'
			 + '</html>'; 

	 response.writeHead(200, { 
		"Content-Type" : "text/html"
	 }); 
	 response.write(body); 
	 response.end(); 

 } 

该函数负责处理 GET 请求,随机向用户返回一条谚语。细心的读者可能会发现该函数将 HTML,CSS 以及数据混在一块儿,显然不符合 MVC 的编程模式。Node.js 有不少第三方开发的模块,其中 express就是一款优秀的 Web 开发框架,有兴趣的读者能够研究一下。


清单 9. 用户输入表单(proverbs.js)
				 
 function addProverb(response) { 
	 var body = '<html>'
			 + '<head>'
			 + '<meta http-equiv="Content-Type" content="text/html; '
			 + 'charset=UTF-8" />'
			 + '</head>'
             + '<body style="font-size: 4em;line-height: 1.2; margin-top: 200;">'
			 + '<form action="/add" method="post">'
			 + '<textarea name="text" rows="10" cols="60"></textarea><p>'
			 + '<input type="submit" value="Submit" 
			    />' + '</form>' + '</body>'
			 + '</html>'; 

	 response.writeHead(200, { 
		"Content-Type" : "text/html"
	 }); 
	 response.write(body); 
	 response.end(); 

 } 

该函数返回一个 HTML 表单,容许用户输入本身喜欢的谚语或格言。


清单 10. POST 请求(proverbs.js)
				 
 function add(proverb, response) { 
	 proverbs.push(proverb); 

	 var body = '<html>'
			 + '<head>'
			 + '<meta http-equiv="Content-Type" content="text/html; '
			 + 'charset=UTF-8" />'
			 + '</head>'
             + '<body style="font-size: 4em;line-height: 1.2; margin-top: 200;">'
			 + '<blockquote>' + proverb + '</blockquote>' + '</body>'
			 + '</html>'; 

	 response.writeHead(200, { 
		"Content-Type" : "text/html"
	 }); 
	 response.write(body); 
	 response.end(); 

 } 

该函数负责用户的 POST 请求,将用户输入保存到服务器端,并返回给用户结果。

结束语

本文给你们介绍了基于事件的编程模型,这种编程模型正是 Node.js 这项最近流行技术的核心,但愿读者能利用 Node.js 的优点,为本身的开发工做带来便利。


参考资料

学习

  • 参考 nodejs官方主页,了解 Node.js。 

  • The Node Beginner Book:一个很是好的 Node.js 教程,同时介绍了 JavaScript 中的高级特性。 

  • Understanding Node.js:一篇有助于理解 Node.js 的文章。 

  • Understanding the node.js event loop深刻介绍了基于事件驱动的编程模型,并与传统的处理方式进行对比。 

  • Node.js 到底是什么?”(developerWorks,2011 年 6 月):大家中的许多人可能据说过新近出现的 Node.js,也许如今还在猜想它到底是何物?Michael Abernethy 在本文中简要介绍了 Node 的做用及其当前局限性。

  • 使用 Node.js 做为完整的云环境开发堆栈”(developerWorks,2011 年 8 月):本文探讨 Node.js,这是一个用于 UNIX 类平台上 V8 JavaScript 引擎的事件驱动的 I/O 框架,设计这一框架的目的是为了编写可伸缩的网络程序,如 Web 服务器。本文经过一个完整的例子说明如何在 Node.js 中构建聊天服务器,分析了这个框架以及围绕它的生态系统(包括云计算产品),并对这个框架进行了总结。

  • 使用 node.js 进行服务器端 JavaScript 编程”(developerWorks,2011 年 7 月):node.js 是一个可使用 JavaScript 开发服务器端应用的平台。它依托于 Google V8 JavaScript 引擎,并采用事件 I/O 的架构,能够用来建立高性能服务器。本文详细介绍了 node.js 的基本知识、模块化的结构、事件驱动的机制以及经常使用的模块。

  • 面向 Java 开发人员的 Node.js”(developerWorks,2011 年 12 月):Node.js 是一种激动人心的开发方式,可替代传统的 Java 并发性,只要有一个开放的心态和一点点 JavaScript 知识,就能够当即开始进行开发。

  • developerWorks Web development 专区:经过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。

  • developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括不少文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。

  • developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。您还能够经过 Web 2.0 新手入门 栏目,迅速了解 Web 2.0 的相关概念。

讨论

  • 加入 developerWorks 中文社区。查看开发人员推进的博客、论坛、组和维基,并与其余 developerWorks 用户交流。
相关文章
相关标签/搜索