1 从名字说起
有关Node.js的技术报道越来越多,Node.js的写法也是五花八门,有写成 NodeJS的,有写成Nodejs的,到底哪一种写法最标准呢,我们不妨遵循官方的说法。在Node.js的官方网站上,一直将其项目称之为”Node“或者”Node.js“
, 没有发现其他的说法,”Node“用的最多,考虑到Node这个单词的意思和用途太广泛,容易让开发人员误解,我们采用了第二种称呼——”Node.js“,js的后缀点出了Node项目的本意
,其他的名称五花八门,没有确切的出处,我们不推荐使用。
2 Node旨在解决什么问题
Node公开宣称的目标是 “旨在提供一种简单的构建可伸缩网络程序的方法”
。当前的服务器程序有什么问题?我们来做个数学题。在 Java™ 和 PHP 这类语言中,每个连接都会生成一个新线程,每个新线程可能需要 2 MB 的配套内存。在一个拥有 8 GB RAM 的系统上,理论上最大的并发连接数量是 4,000 个用户。随着您的客户群的增长,如果希望您的 Web 应用程序支持更多用户,那么,您必须添加更多服务器。当然,这会增加服务器成本、流量成本和人工成本等成本。除这些成本上升外,还有一个潜在技术问题,即用户可能针对每个请求使用不同的服务器,因此,任何共享资源都必须在所有服务器之间共享。 鉴于上述所有原因,整个 Web 应用程序架构(包括流量、处理器速度和内存速度)中的瓶颈是:服务器能够处理的并发连接的最大数量
。
Node 解决这个问题的方法是:更改连接到服务器的方式
。每个连接发射一个在 Node 引擎的进程中运行的事件,而不是为每个连接生成一个新的 OS 线程(并为其分配一些配套内存)。Node 声称它绝不会死锁,因为它根本不允许使用锁,它不会直接阻塞 I/O 调用
。Node 还宣称,运行它的服务器能支持数万个并发连接。
3 Node.js不是JS应用、而是JS运行平台
看到Node.js这个名字,初学者可能会误以为这是一个Javascript应用, 事实上,Node.js采用C++语言编写而成,是一个Javascript的运行环境
。为什么采用C++语言呢?据Node.js创始人Ryan Dahl回忆,他最初希望采用Ruby来写 Node.js,但是后来发现Ruby虚拟机的性能不能满足他的要求,后来他尝试 采用V8引擎,所以选择了C++语言
。既然不是Javascript应用,为何叫.js呢? 因为 Node.js是一个Javascript的运行环境
。提到Javascript,大家首先想到的是日常使用的浏览器,现代浏览器包含了各种组件,包括渲染引擎、Javascript引擎 等,其中Javascript引擎负责解释执行网页中的Javascript代码。作为Web前端最重要的语言之一,Javascript一直是前端工程师的专利。不过, Node.js是一个后端的Javascript运行环境(支持的系统包括Linux、Windows)
,这意味着你可以 编写系统级或者服务器端的Javascript代码,交给Node.js来解释执行
,简单的命令类似于:
#node helloworld.js
Node.js采用了Google Chrome浏览器的V8引擎,性能很好, 同时还提供了很多系统级的API
,如文件操作、网络编程等。浏览器端的Javascript代码在运行时会受到各种安全性的限制,对客户系统的操作有限
。相比之下, Node.js则是一个全面的后台运行时,为Javascript提供了其他语言能够实现的许多功能
。
4 Node.js采用事件驱动、异步编程, 为网络服务而设计
事件驱动这个词并不陌生,在某些传统语言的网络编程中,我们会用到回调函数,比如当socket资源达到某种状态时,注册的回调函数就会执行。Node.js的设计思想中以事件驱动为核心,它提供的绝大多数API都是基于事件的、异步的风格
。以Net模块为例,其中的net.Socket对象就有以下事件:connect、data、 end、timeout、drain、error、close等,使用Node.js的开发人员需要根据自己的业务逻辑注册相应的回调函数。这些回调函数都是异步执行的
,这意味着虽然在代码结构中,这些函数看似是依次注册的,但是 它们并不依赖于自身出现的顺序,而是等待相应的事件触发
。事件驱动、异步编程的设计,重要的优势在于,充分利用了系统资源,执行代码无须阻塞等待某种操作完成,有限的资源可以用于其他的任务
。此类设计非常适合于 后端的网络服务编程,Node.js的目标也在于此
。在服务器开发中,并发的请求处理是个大问题,阻塞式的函数会导致资源浪费和时间延迟。通过事件注册、异步函数,开发人员可以提高资源的利用率,性能也会改善。
从Node.js提供的支持模块中,我们可以 看到包括文件操作在内的许多函数都是异步执行的
,这和传统语言存在区别,而且为了方便服务器开发,Node.js的网络模块特别多,包括HTTP、DNS、NET、UDP、HTTPS、TLS等,开发人员可以在此基础上快速构建Web服务器。以简单的helloworld.js为例:
// 全局方法require()是用来导入模块的,一般直接把require()方法的返回值赋值给一个变量,在JavaScript代码中直接使用此变量即可。require("http")就是加载系统预置的http模块。
var http = require('http');
// http.createServer是模块的方法,目的就是创建并返回一个新的web server对象,并且给服务绑定一个回调,用以处理请求。
http.createServer(function (req, res) {
// 使用response.writeHead()函数发送一个HTTP状态200和HTTP头的内容类型(content-type)
// 使用response.write()函数在HTTP相应主体中发送文本“Hello World"
res.writeHead(200, {'Content-Type': 'text/plain'});
// 完成响应
res.end('Hello World\n');
// 通过http.listen()方法就可以让该HTTP服务器在特定端口监听。
}).listen(80, "127.0.0.1");
// console.log就不用多说了,了解firebug的都应该知道,Node实现了这个方法。
console.log('Server running at http://127.0.0.1:80/');
上面的代码搭建了一个简单的http服务器(运行示例部署 在http://127.0.0.1中可以访问),在本地监听80端口,对于任意的http请求,服务器都返回一个头部状态码为200、Content-Type值为'text/plain'的"Hello World"文字响应。从这个小例子中,我们可以看出几点:
- Node.js的网络编程比较便利,提供的模块(在这里是http)开放了容易上手的API接口,短短几行代码就可以构建服务器。
- 体现了事件驱动、异步编程,在createServer函数的参数中指定了一个回调函数(采用Javascript的匿名函数实现),当有http请求发送过来时,Node.js就会调用该回调函数来处理请求并响应。当然,这个例子相对简单,没有太多的事件注册,在以后的文章中读者会看到更多的实际例子。
当我们使用 http.createServer 方法的时候,我们当然不只是想要一个侦听某个端口的服务器,我们还想要它在服务器收到一个HTTP请求的时候做点什么。问题是,这是异步的:请求任何时候都可能到达,但是我们的服务器却跑在一个单进程中
。我们创建了服务器,并且向创建它的方法传递了一个函数。无论何时我们的服务器收到一个请求,这个函数就会被调用
。
为什么这种事件驱动对 Node 很理想?JavaScript 是一种很棒的事件驱动编程语言
,因为它允许使用匿名函数和闭包,更重要的是,任何写过代码的人都熟悉它的语法。事件发生时调用的回调函数可以在捕获事件处进行编写。这样可以使代码容易编写和维护,没有复杂的面向对象框架,没有接口,没有过度设计的可能性。只需监听事件,编写一个回调函数,其他事情都可以交给系统处理!
5 Node.js的特点
下面我们来说说Node.js的特点。事件驱动、异步编程的特点刚才已经详细说过了,这里不再重复。
Node.js的性能不错
。按照创始人Ryan Dahl的说法,性能是Node.js考虑的重要因素, 选择C++和V8而不是Ruby或者其他的虚拟机也是基于性能的目的
。Node.js在设计上也是比较大胆, 它以单进程、单线程模式运行(很吃惊,对吧?这和Javascript的运行方式一致),事件驱动机制是Node.js通过内部单线程高效率地维护事件循环队列来实现的,没有多线程的资源占用和上下文切换,这意味着面对大规模的http请求,Node.js凭借事件驱动搞定一切
,习惯了传统语言的网络服务开发人员可能对多线程并发和协作非常熟悉,但是面对 Node.js,我们需要接受和理解它的特点。由此我们是否可以推测出这样的设计会 导致负载的压力集中在CPU(事件循环处理?)而不是内存(还记得Java虚拟机抛出OutOfMemory异常的日子吗?)
, 眼见为实,不如来看看淘宝共享数据平台团队对Node.js的性能测试:
- 物理机配置:RHEL 5.2、CPU 2.2GHz、内存4G
- Node.js应用场景:MemCache代理,每次取100字节数据
- 连接池大小:50
- 并发用户数:100
- 测试结果(socket模式):内存(30M)、QPS(16700)、 CPU(95%)
从上面的结果,我们可以看到在这样的测试场景下,qps能够达到16700次,内存仅占用30M(其中V8堆占用22M),CPU则达到95%,可能成为瓶颈。此外,还有不少实践者对Node.js做了性能分析,总的来说,它的性能让人信服, 也是受欢迎的重要原因。既然Node.js采用单进程、单线程模式,那么在如今多核硬件流行的环境中,单核性能出色的Node.js如何利用多核CPU呢?创始人Ryan Dahl建议,运行多个Node.js进程,利用某些通信机制来协调各项任务
。目前,已经有不少第三方的Node.js多进程支持模块发布,后面的文章会详细讲述Node.js在多核CPU下的编程。
Node.js的另一个特点是它支持的编程语言是Javascript。关于动态语言和静态语言的优缺点比较在这里不再展开讨论。只说三点:
- Javascript作为前端工程师的主力语言,在技术社区中有相当的号召力。而且,随着Web技术的不断发展,特别是前端的重要性增加,不少前端工程师开始试水”后台应用“,在许多采用Node.js的企业中,工程师都表示因为习惯了Javascript,所以选择Node.js。
- Javascript的匿名函数和闭包特性非常适合事件驱动、异步编程, 从helloworld例子中我们可以看到回调函数采用了匿名函数的形式来实现,很方便。闭包的作用则更大,看下面的代码示例:
var hostRequest = http.request(requestOptions,function(response) {
var responseHTML ='';
response.on('data', function (chunk) {
responseHTML = responseHTML + chunk;
});
response.on('end',function(){
console.log(responseHTML);
// do something useful
});
});
在上面的代码中,我们需要在end事件中处理responseHTML变量, 由于Javascript的闭包特性,我们可以在两个回调函数之外定义responseHTML变量
,然后在data事件对应的回调函数中不断修改其值,并最终在end事件中访问处理。
-
Javascript在动态语言中性能较好, 有开发人员对Javacript、Python、Ruby等动态语言做了性能分析,发现Javascript的性能要好于其他语言, 再加上V8引擎也是同类的佼佼者,所以Node.js的性能也受益其中。选择Node.js有许多方面的原因,比如考虑了兴趣及社区发展,
同时也希望可以提高并发能力,榨干CPU
。