Node.js开发实战
上QQ阅读APP看书,第一时间看更新

1.2 Node.js的发展历史和特点

任何语言或框架都不是一天形成的,而是经过漫长的测试、发布、再测试、再发布的迭代过程。本节就来介绍一下Node.js的发展过程。

1.2.1 Node.js发展历史

Node.js的创始人是大名鼎鼎的Ryan Dahl。他本来是学数学的,2008年末一个偶然的机会让他了解到Google推出了一个新的浏览器Chrome和崭新的JavaScript引擎V8。他听说这是一个为了更快的Web体验而专门制作的更快的JavaScript引擎,V8能够让Web应用大大提速。当时,他正在寻找一个新的编程平台来做网站,他非常希望能找到一种语言能提供先进的推送功能并集成到网站中,而不是采用传统的方式——不断轮询拉取数据。

Ryan Dahl对C/C++和系统调用非常熟悉,他使用系统调用(用C)实现消息推送这样的功能。如果只使用非阻塞式Socket,每个连接的开销都会非常小。在小规模测试中,它能同时处理几千个闲置连接,并可以实现相当大的吞吐量。但是,他并不想使用C,他希望能采用另外一种漂亮灵活的动态语言。他最初也希望采用Ruby来写Node.js,但是后来发现Ruby虚拟机的性能不能满足要求,后来便尝试采用V8引擎,所以选择了C++语言。

2009年2月,Ryan Dahl首次在自己的博客上宣布准备基于V8创建一个轻量级的Web服务器并提供一套库,并在2009年5月正式在GitHub上发布最初版本的部分Node.js包。随后几个月里,有人开始使用Node.js开发应用。实践证明,JavaScript与非阻塞Socket配合得相当完美,只需要简单的几行JavaScript代码就可以构建出非常复杂的非阻塞服务器。

2010年年底,Node.js获得云计算服务商Joyent资助。创始人Ryan Dahl加入Joyent,全职负责Node.js的发展。Node.js从此以后迅猛发展,并成为一种流行的开发语言。

在官方网站上Node.js的版本号是从0.1.14开始的,每个发布版本对应不同的V8引擎版本和NPM包管理器版本,截止作者写作时最新的版本为V6.11.0,其各个版本的具体发布时间参见表1-1(2014年以后)。

表1-1 Node.js版本

提示

从1.x到3.x的版本之间Node.js的名称曾经被修改为io.js,后来io.js的全部代码合并到Node.js的主干发布版本。

Node.js的发展大致可以分为以下4个阶段。

(1)发展初期。创始人Ryan Dahl带着他的团队开发出以Web为中心的Web.js,一切都非常混乱,API大多都处于研究阶段。

(2)快速发展时期。Node.js的核心用户Isaac Z. Schlueter开发出奠定了Node.js如今地位的重要工具——NPM,同时也为他后来成为Ryan接班人的重要条件。之后Connect、Express、Socket.io等库的出现吸引了一大波爱好者加入到Node.js开发者的阵营中来。CoffeeScript的出现更是让不少Ruby和Python开发者找到了学习的理由。其间一大波以Node.js作为运行环境的CLI工具涌现,其中不乏用于加速前端开发的优秀工具,如Less、UglifyJS、Browserify、Grunt等。这个阶段Node.js的发展势如破竹。

(3)不稳定时期。经过了一大批一线工程师的探索实践后,Node.js开始进入时代的更迭期,新模式代替旧模式,新技术代替旧技术,好实践代替旧实践。ES 6也开始出现在Node.js世界中。ES 6的发展越来越明显,V8也对ES 6中的部分特性实现了支持,如Generator等。

(4)稳步发展时期。随着ES 2015的发展和最终定稿,一大批利用ES 2015特性开发的新模块出现,如原Express核心团队所开发的Koa。Node.js之父Ryan Dahl退出Node.js的核心开发,转而做其他的研究项目。Ryan Dahl的接任者Isaac Schlueter(也是Node.js的核心构建者)将Node.js一直开发下去并不断完善。

1.2.2 Node.js未来版本规划

Node.js的核心团队已经为Node.js的长远发展做好了详细计划。图1.1是Node.js到2018年为止的所有版本计划及发布时间表。

图1.1 计划及发布时间表

1.2.3 Node.js的结构

前面介绍了Node.js是一个完整的JavaScript开发环境,并且是基于Google的Chrome V8引擎进行代码解释的。它在设计之初就已经定位用来解决传统Web开发语言所遇到的诸多问题,所以Node.js有很多其他开发语言所不具备的优点,包括事件驱动、异步编程等。下面我们先介绍一下Node.js的结构,然后详细分析Node.js的一些主要特点。

图1.2中浅绿色的部分是由JavaScript编写的,深绿色的是由C/C++完成的。从图1.2中可以看出来,Node.js的结构大致分为以下3个层次。

  • Node.js标准库。这部分是由JavaScript编写的,即我们使用过程中直接能调用的API。在源码中的lib目录下可以看到。
  • Node bindings。这一层是JavaScript与底层C/C++能够沟通的关键,前者通过bindings调用后者,相互交换数据。
  • 支撑Node.js运行的基础构件。这层内容比较多,下面详细介绍。

支撑Node.js运行的基础构件是由C/C++实现的,其中包括4大部分。

  • V8:Google推出的JavaScript VM,也是Node.js为什么用JavaScript的关键,它为JavaScript提供了在非浏览器端运行的环境,它的高效是Node.js之所以高效的原因之一。
  • libuv:为Node.js提供了跨平台、线程池、事件池、异步I/O等能力,是Node.js如此强大的关键。
  • C-ares:提供了异步处理DNS相关的能力。
  • http_parser、OpenSSL、zlib等:提供包括HTTP解析、SSL、数据压缩等能力。

图1.2 Node.js的结构

1.2.4 Node.js的特点

都说Node.js强大,这种强大体现在很多方面,如事件驱动、异步处理、非阻塞I/O等。这里将介绍Node.js具备的不同于其他框架的特点。

1.事件驱动

在某些传统语言的网络编程中,我们会用到回调函数,比如当Socket资源达到某种状态时,注册的回调函数就会执行。Node.js的设计思想中以事件驱动为核心,它提供的绝大多数API都是基于事件的、异步的风格。以Net模块为例,其中的net.Socket对象就有以下事件:connect、data、end、timeout、drain、error、close等。使用Node.js的开发人员需要根据自己的业务逻辑注册相应的回调函数。这些回调函数都是异步执行的。这意味着虽然在代码结构中这些函数看似是依次注册的,但是它们并不依赖于自身出现的顺序,而是等待相应的事件触发。

事件驱动的优势在于充分利用了系统资源,执行代码无须阻塞等待某种操作完成,有限的资源可以用于其他的任务。此类设计非常适合于后端的网络服务编程,Node.js的目标也在于此。在服务器开发中,并发的请求处理是一个大问题,阻塞式的函数会导致资源浪费和时间延迟。通过事件注册、异步函数,开发人员可以提高资源的利用率,性能也会改善。

2.异步、非阻塞I/O

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

一个异步I/O的大致流程如图1.3所示,讲解如下:

(1)发起I/O调用

① 用户通过JavaScript代码调用Node核心模块,将参数和回调函数传入核心模块。

② Node核心模块会将传入的参数和回调函数封装成一个请求对象。

③ 将这个请求对象推入I/O线程池等待执行。

④ JavaScript发起的异步调用结束,JavaScript线程继续执行后续操作。

(2)执行回调

① I/O操作完成后会将结果储存到请求对象的result属性上,并发出操作完成的通知。

② 每次事件循环时会检查是否有完成的I/O操作,如果有就将请求对象加入I/O观察者队列中,之后当作事件处理。

③ 处理I/O观察者事件时会取出之前封装在请求对象中的回调函数,执行这个回调函数,并将result当作参数,以完成JavaScript回调的目的。

Node.js的网络编程非常方便,提供的模块(在这里是HTTP)开放了容易上手的API接口,短短几行代码就可以构建服务器。

图1.3 异步I/O的流程

3.性能出众

创始人Ryan Dahl在设计的时候就考虑了性能方面的问题,选择C++和V8,而不是Ruby或者其他的虚拟机。Node.js在设计上以单进程、单线程模式运行。事件驱动机制是Node.js通过内部单线程高效率地维护事件循环队列来实现的,没有多线程的资源占用和上下文切换。这意味着面对大规模的HTTP请求,Node.js是凭借事件驱动来完成的。从大量的测试结果分析来看,Node.js的处理性能是非常出色的,在qps达到16700次时,内存仅占用30MB(测试环境:RHEL 5.2、CPU 2.2GHz、内存4GB)。

4.单线程

Node.js和大名鼎鼎的Nginx一样,都是以单线程为基础的。这正是Node.js保持轻量级和高性能的关键,也是Ryan Dahl设计Node.js的初衷。这里的单线程是指主线程为“单线程”,所有阻塞的部分交给一个线程池处理,然后这个主线程通过一个队列跟线程池协作。我们写的js代码部分不用再关心线程问题,代码也主要由一堆callback回调构成,然后主线程在循环过程中适时调用这些代码。

单线程除了保证Node.js高性能之外,还保证了绝对的线程安全,使开发者不用担心同一变量同时被多个线程读写而造成的程序崩溃。

1.2.5 Node.js的应用场景

Node.js的可以应用到很多方面,可以说从Node.js开始程序员们可以使用JavaScript来开发服务器端的程序了。Node.js为前端开发程序员们提供了便利,并在各大网站中承担重要角色,成为开发高并发大型网络应用的关键技术。Web站点早已不仅限于内容的呈现,很多交互性和协作型环境也逐渐被搬到了网站上,而且这种需求还在不断地增长。这就是所谓的数据密集型实时(data-intensive real-time)应用程序,比如在线协作的白板、多人在线游戏等,这种Web应用程序需要一个能够实时响应大量并发用户请求的平台支撑它们,这正是Node.js擅长的领域。此外,Node.js的跨平台特性也是使用Node.js语言开发流行的另一大原因。

Node.js的主要应用场景如下:

  • JSON APIs——构建一个Rest/JSON API服务,Node.js可以充分发挥其非阻塞IO模型以及JavaScript对JSON的功能支持(如JSON.stringfy函数)。
  • 单页面、多Ajax请求应用——如Gmail,前端有大量的异步请求,需要服务后端有极高的响应速度。
  • 基于Node.js开发UNIX命令行工具——Node.js可以大量生产子进程,并以流的方式输出,这使得它非常适合做UNIX命令行工具。
  • 流式数据——传统的Web应用,通常会将HTTP请求和响应看成原子事件,而Node.js会充分利用流式数据这个特点,构建非常酷的应用,如实时文件上传系统transloadit。
  • 准实时应用系统——如聊天系统、微博系统,但JavaScript是有垃圾回收机制的,这就意味着系统的响应时间是不平滑的(GC垃圾回收会导致系统这一时刻停止工作)。如果想要构建硬实时应用系统,Erlang是一个不错的选择。

例如,实时互动交互比较多的社交网站,像Twitter这样的公司,它必须接收tweets并将其写入数据库。实际上,每秒几乎有数千条tweet达到,数据库不可能及时处理高峰时段所需的写入数量。Node成为这个问题解决方案的重要一环。Node能处理数万条入站tweet。它能快速而又轻松地将它们写入一个内存排队机制(例如memcached),另一个单独进程可以从那里将它们写入数据库。Node能处理每个连接而不会阻塞通道,从而能够捕获尽可能多的tweets。

虽然看起来Node.js可以做很多事情,并且拥有很高的性能,但是Node.js并不是万能的,有一些类型的应用,Node.js可能处理起来会比较吃力。例如,CPU密集型的应用、模板渲染、压缩/解压缩、加/解密等操作都是Node.js的软肋。