摘要:
如果经常浏览各大技术网站,那么你会发现自己很难错过 Node.js,程序员们兴奋地说:JavaScript 也可以开发服务器端的程序了!没错,Node.js 的确为我们提供了这种便利,前台后台都用同一种语言实现。但这种兴奋,却极易掩盖 Node.js 另外一个重要的特性:基于事件驱动的编程模型。而后者,才真正使得 Node.js 成为开发高并发大型网络应用的关键。本文详细介绍了 Node.js 的核心,即基于事件驱动的编程模型,并与传统的单线程和多线程编程模型进行对比。文章最后提供了一个完整的 Web 应用,帮助您理解 Node.js 的编程方式。
0、引言
Node.js 被设计用来开发大规模高并发的网络应用,这种网络应用的瓶颈之一是在 I/O 的处理效率上。由于硬件及网络的限制,I/O 的速度往往是固定的,如何在此前提下尽可能处理更多的客户请求,提高 CPU 使用效率,便成了开发人员面临的最大问题。得益于基于事件驱动的编程模型,Node.js 使用单一的 Event loop 线程处理客户请求,将 I/O 操作分派至各异步处理模块,既解决了单线程模式下 I/O 阻塞的问题,又避免了多线程模式下资源分配及抢占的问题。至于使用 JavaScript 开发服务器端代码,这并不是什么新鲜事物,JavaScript 本来就是一种完备的编程语言,微软的 IIS 服务器很早就支持 JavaScript 在其中运行。本文将重点讲述 Node.js 基于事件的编程模型,并与传统的处理方式进行对比,帮助您更好的理解 Node.js。
1、网络应用的性能瓶颈
网络应用的性能瓶颈之一在于 I/O 处理上,下表来自 Node.js 的作者 Ryan Dahl 为 JSConf 大会所作的 讲演,对比了在不同介质上进行 I/O 操作所花费的 CPU 时间。您能够清楚的发现,访问磁盘及网络数据所花费的 CPU 时间是访问内存时的数十万倍,而现在的网络应用,却需要大量的访问磁盘及网络,比如数据库查询、访问互联网等。如何提高此时 CPU 的利用效率,便成了提升网络应用性能的关键。
表 1. 不同介质下 I/O 操作花费对比
I/O | CPU Cycle |
L1-cache | 3 |
L2-cache | 14 |
RAM | 250 |
Disk | 41000000 |
Network | 240000000 |
2、传统的处理方式
2.1 单线程下的阻塞式 I/O
var result =
db.query("select * from T");
// 使用该查询结果
上述代码描述了一个常见的案例,客户端发起一个 I/O 请求,然后等待服务器端返回 I/O 结果,结果返回后再对其进行操作,但这种请求常常需要很长时间(对于服务器的 CPU 处理能力来说)。这一过程中,服务器无法接受新的请求,即阻塞式 I/O。这种处理方式虽然简单,却不实用,尤其是面对大量请求的时候,简直就不可用。这种情景类似在火车站售票窗口排队买票,如果您在春节期间去北京火车站排队买过票,绝不会认为这是一种好的处理方式。庆幸的是,现在很少有服务器采取这种处理方式。
2.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
4、Node.js 简介
有了上面对于事件处理编程模型的介绍,Node.js 就很好理解了。Node.js 是采用事件处理编程模型的 JavaScript 平台,它允许程序员开发大规模高并发的网络应用。这个概念并不新鲜,在 Node.js 之前,很多语言都提供了类似的平台:Python 的 Twisted,Perl 的 AnyEvent,Ruby 的 EventMachine。Node.js 优于其他平台的另一个好处是所有的 I/O 操作都以异步方式实现,让程序员将主要精力放在应用的业务逻辑上。
5、为什么选用 JavaScript
事实上,在实现 Node.js 之初,作者 Ryan Dahl 并没有选择 JavaScript,他尝试过 C、Lua,皆因其欠缺一些高级语言的特性,如闭包、函数式编程,致使程序复杂,难以维护。而 JavaScript 则是支持函数式编程范型的语言,很好地契合了 Node.js 基于事件驱动的编程模型。加之 Google 提供的 V8 引擎,使 JavaScript 语言的执行速度大大提高。最终呈现在我们面前的就成了 Node.js,而不是 Node.c,Node.lua 或其他语言的实现。
6、一个例子
本文将在这里使用 Node.js 实现一个小型的 Web 应用,它将随机为用户显示一条谚语或名人名言,并允许浏览者添加自己喜欢的谚语。用 Node.js 开发 Web 应用非常简单,下面这段一百多行的代码就实现了一个完整的应用。如果您尚未安装好 Node.js,请登录其 官方网站查看详细安装说明。
6.1 导入所需要的模块(proverbs.js)
// 导入所需模块
var http = require("http");
var url = require("url");
var qs = require('querystring');
首先需要导入该应用所需要的模块,其中 http 模块负责创建 Web 服务器及 HTTP 相关服务,url 模块负责解析 URL 地址,querystring 模块负责处理请求参数。
6.2 数据存储(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.3 创建 Web 服务器(proverbs.js)
// 创建一个 Web 服务器
http.createServer(onRequest).listen(8888);
console.log("server is running...");
使用 Node.js 开发 Web 应用非常简单,甚至不用配置 Web 服务器,一行代码就创建成功一个 Web 服务器,同时传入一个回调函数,服务器创建成功后,代码并没有阻塞到那里,而是接着往下执行,这就是事件驱动模型的编程风格,在 Node.js 里将会大量采用这种方式。
6.4 请求处理函数(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 根据规则转发至对应的请求处理模块。
6.5 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 开发框架,有兴趣的读者可以研究一下。
6.6 用户输入表单(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 表单,允许用户输入自己喜欢的谚语或格言。
6.7 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 请求,将用户输入保存到服务器端,并返回给用户结果。
7、结束语
本文给大家介绍了基于事件的编程模型,这种编程模型正是 Node.js 这项最近流行技术的核心,希望读者能利用 Node.js 的优势,为自己的开发工作带来便利。
本文原地址 http://www.ibm.com/developerworks/cn/web/1201_wangqf_nodejs/index.html
- 大小: 3.4 KB
分享到:
相关推荐
标题中的“Node.js-头尾GridView支持AUTOFIT模式头尾模式有两种无反射代码”实际上是一种误解,因为Node.js是JavaScript的服务器端运行环境,而GridView是Android开发中的一个视图组件,通常用于显示二维网格布局的...
这一点经常让初学者产生误解,认为Node.js本身就是JavaScript的应用。实际上,Node.js是一个可以运行JavaScript代码的平台。 2. **高性能的V8引擎**:Node.js使用了Google Chrome浏览器的V8 JavaScript引擎,这...
标题中的“Node.js”可能让人误解,实际上,Node.js在这里是指Node包管理器(NPM),它是React Native生态系统的一部分,用于安装和管理项目依赖,包括这个原生导航库。不过,这个库本身并不直接使用Node.js运行时,...
本项目标题为“Node.js-一行代码实现android底部导航栏”,但实际上,Node.js是服务器端的JavaScript运行环境,与Android客户端开发中的底部导航栏实现无关。这里可能是个误解,我们主要关注的是Android端的实现。 ...
被误解的Node.js:除了性能,都是病? 现代的Node.js:构建微服务利器 个人介绍 i5ting(江湖人称狼叔),Node.js 技术布道者,已出版《更了不起的 Node :将下一代 Web 框架 Koa 进行到底》 曾就职在新浪、网秦,曾...
在基于Node.js实现微信支付退款功能的过程中,开发者需要遵循微信官方提供的API接口规则,并处理与退款相关的业务逻辑。 首先,开发者需要了解微信支付退款的流程。当用户发起退款时,商家系统首先接收到用户的退款...
标题中的“ZZod:Node.js试用”表明这是一个关于Node.js的实践项目,可能是为了学习和测试Node.js环境以及相关的JavaScript编程技术。Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它允许开发者在服务器端...
不过,由于JavaScript广泛应用于服务器端的Node.js环境,因此这个标签可能意味着该库也可能被用在基于Java生态系统的Node.js项目中。 **文件名称列表解析:** 由于提供的列表只有一个条目 "js库",这可能意味着...
标题中的“Node.js-Profilo”可能是个误解,因为Profilo实际上是一个专门针对Android平台的性能分析库,并非与Node.js直接相关。不过,我们可以深入探讨一下Android开发中的性能优化以及Profilo库在其中的作用。 在...
Node.js编码规范是提高代码可读性、可维护性和团队协作...以上规范基于《Node.js开发指南》及阮一峰的《Javascript编程风格》等资料。遵循这些规范,可以帮助开发者编写出更高质量的Node.js代码,提升整体项目质量。
Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它允许开发者在服务器端使用JavaScript进行编程,实现了全栈开发的可能性。这个案例可能是为了帮助初学者了解如何用Node.js构建一个简单的应用。 描述 "基于...
如果您听说过基于JavaScript比如一些游戏引擎Egret , Cocos2d-js和layabox ,那么你会意识到你误解这个词。Node.js npm install hasdom var hasDOM = require ( '../dist/hasdom' ) . hasDOMconsole . log ( 'dom ...
Node.js 6.0.0版本之前,存在一个安全漏洞,即Buffer构造函数可以接受一个数字参数,该数字会被误解为缓冲区的大小,但实际上可以是一个任意的整数。这可能导致内存分配错误,Node.js 6.0.0开始引入了new Buffer()的...
了解了这些特定情况,我们可以得出结论,Node.js中的加分号遵循的原则是:在代码块的结束花括号后、以`[`或``开头的行开始处,以及在任何有可能导致JavaScript引擎误解语句边界的地方,都需要手动添加分号。...
2. Understanding JavaScript Function Invocation and "this"(理解JavaScript函数调用和“this”):这部分内容解释了在JavaScript中函数如何被调用以及如何...,这是JavaScript中一个容易误解和被误用的部分...
将Node.js上的WebSocket和Socket.IO与Express.js进行比较-示例服务器端和客户端代码,以查看Express.js应用程序中WebSocket和Socket.IO的基本用法,并提供有关如何比较实际的网络流量(许多在线资源通常会误解)。...
node-macaddress, 获取主机网络接口的MAC地址( 硬件地址) 节点 macaddress 在 Linux,OS X 和 Windows 中检索MAC地址。关于MAC地址的常见误解是每个主机 had MAC MAC地址,而主机可能有位MAC地址,而主机的MAC地址...
描述中提到这个项目是用Java开发的,但标题中明确指出是使用Node.js和Express,因此可能存在误解或者描述错误。通常情况下,描述中的“Java开发”可能是指系统的某些部分或与之相关的其他服务或接口采用了Java技术。...
Node.js学习总结之调式代码的方法主要涵盖了代码调试的概念、调试的重要性以及具体的调试策略。通过类比警察破案的方式,指出调试代码的目的是为了保证系统的稳定性,强调了在调试过程中不应冤枉任何代码片段。以下...
JavaScript是一个被误解的语言,它曾经是泛滥的网页特效和弹窗广告的代名词,但随着Ajax应用的流行和前端框架的发展,JavaScript已经越来越严格和标准化。本篇文章将通过几个简单的JavaScript语句,来了解JavaScript...