被误解的 Node.js

https://www.ibm.com/developerworks/cn/web/1201_wangqf_nodejs/

 

被误解的 Node.js

简介: 如果经常浏览各大技术网站,那么你会发现自己很难错过 Node.js,程序员们兴奋地说:JavaScript 也可以开发服务器端的程序了!没错,Node.js 的确为我们提供了这种便利,前台后台都用同一种语言实现。但这种兴奋,却极易掩盖 Node.js 另外一个重要的特性:基于事件驱动的编程模型。而后者,才真正使得 Node.js 成为开发高并发大型网络应用的关键。本文详细介绍了 Node.js 的核心,即基于事件驱动的编程模型,并与传统的单线程和多线程编程模型进行对比。文章最后提供了一个完整的 Web 应用,帮助您理解 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。

 

网络应用的性能瓶颈

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


表 1. 不同介质下 I/O 操作花费对比
I/OCPU Cycle
L1-cache3
L2-cache14
RAM250
Disk41000000
Network240000000

 

传统的处理方式

单线程


清单 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 或其他语言的实现。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值