深入浅出 Nodejs ( 一 ) :Nodejs 的简介

作者:郭泽豪

前言

对于Node初学者,目前市面上已经有Node相关的入门书,比如说《Nodejs开发指南》,它们可以快速领你进Node开发之旅,但是如果你想了解Node更多进阶的知识,我推荐阅读《深入浅出Nodejs》,这本书从内部实现原理的角度来理解Node,深入浅出,使读者能知其然,并知其所以然。

我认为Node是一门独具风格的技术,它的特点很有意思,比如事件驱动、异步I/O、跨平台、单线程等等,你会感觉Node这个技术在颠覆我们对以往编程模型的理解。内心萌生的好奇心使我很想去了解它的本质,而《深入浅出Nodejs》刚好是我现阶段所需要的一本书,磨刀不误砍柴工,我磨完刀就拿这本书开刀了。看完一个章节是我每天必须完成的一个任务,每看完一个章节,我都会龙飞凤舞地把自己对这个章节的理解写在笔记中,不知道地以为是我的笔记加了密,有时连我自己也看不懂我的狂草,所以最近想花点时间把这些可能还不太成熟的草书整理成系列文章的方式分享给大家,希望对大家有所帮助。

《深入浅出Nodejs》这本书,可能会涉及到Node的一些源码,所以我希望阅读本系列文章的读者有涉略过Nodejs方面的书籍,如果对于Nodejs还没有入门可以参照我的KM文章,尝试自己搭建Node环境、安装开发IDE、安装各种依赖包以及用各种依赖包来开发我们的微博网站。

为了让大家能够更好地理解这本书的内容,我会尝试通过运行一些Demo程序方式来帮助大家更好地理解源码。另外由于这本书本身篇幅的限制,书里面虽然提到多个开源的依赖包,比如说作者自己开发的事件处理依赖包EventProxy,但对于这些依赖包作者只是简单地说明它们的功能,对于它们的应用实践方面的介绍偏少,针对这个问题,我会尝试对这些模块就应用部分进行扩展,使得大家的学习更加落地。好的,我们废话少说,开始我们的第一章,Node简介。

本章的重点内容

  • Node的特点,需要理解异步I/O、事件与回调函数、单线程与跨平台
  • Node的应用场景,I/O密集型以及CPU密集型
  • 如何理解与原有的系统和平共处
  • Node的使用者出于什么目的

关于Node的诞生历程,命名与起源,Ryan Dual为什么选择javascript作为Node的实现语言,为什么叫Node以及Node给Javascript带来的意义,我们这里不说,但是我觉得Node开发者还是有必要去了解这部分的内容的,虽然说没有多少技术含量,有兴趣的读者可以自行查阅相关的资料。本教程我们主要讲Node的特点,Node应用场景以及Node的使用者。

一、Node的特点

这里,我们讲Node的四大特点,异步I/O,事件与回调函数,单线程以及跨平台。

1.1 异步I/O

关于异步I/O对于前端工程师来说,理解起来容易一些,因为异步发送Ajax请求对于前端工程师来说是最熟悉不过的场景,下面的代码用于发起一个Ajax请求。

$.post('/url', function(data){

console.log('收到响应');

});

console.log('发送Ajax请求');

运行这段代码,我们发现”收到响应”是在”发送Ajax请求”之后输出的。其实$.post()是一个异步调用,调用后不阻塞后续的代码的执行,等到异步调用返回响应结果后,才执行回调函数function(data)里面的代码。图1是一个经典的Ajax请求。

图1经典的Ajax请求

在Node中,异步I/O也很常见。我们以读文件为例,我们可以发现它与前端Ajax请求的处理方式是极其相似的。

var fs = require('fs');
fs.readFile('/path',function(err, file){
    console.log('文件读取完成');
});
console.log('发起读取文件');

运行后我们同样发现”文件读取完成”也是在”发起读取文件”之后输出的。图2给出fs.readFile()读取文件的异步调用过程。在Node中,绝大多数的操作都以异步的方式进行调用的,从文件读取到网络请求都是如此。

图2 fs.readFile()读取文件的异步调用过程

1.2 事件与回调函数

随着Web2.0时代的到来,JavaScript在前端担任更多的职责,事件得到广泛地应用。Node把在前端应用广泛且成熟的事件引入后端,配置异步I/O,把事件点暴露给业务逻辑。其实在Node的应用中,你会发现复杂的业务逻辑其实就是暴露的事件点之间相互协作来完成的。接下来我们通过一个例子来讲讲Node的事件与回调函数,下面的例子是Ajax异步请求提交给服务器处理的过程。对于服务器,我们通过让其监听3000端口来绑定request事件,对于请求对象,我们为其绑定data事件以及end事件,代码如下所示。

//服务器监听3000端口
var http = require('http');
http.createServer(function(req, res){
    var postData = '';
    //request对象绑定data事件
    req.on('data', function(chunk){
        postData += chunk;
    });
    //request对象绑定end事件
    req.on('end', function(){
        res.end(postData);
    })
}).listen(3000);//绑定request事件

我们在前端向监听3000端口的服务器发起一个Ajax请求,这时服务器的data事件会被触发,从前端接收数据并保存在chunk,如果前端发送的数据很大,会触发多次data事件,每次接收到的chunk会拼接到postData字符串,如果前端的数据发送完成,服务器的end事件会被触发,最后将postData返回给前端。如果Ajax请求处理成功,那么执行success对应的回调函数,如果失败,执行error对应的回调函数,代码如下所示。

//ajax异步请求
$.ajax({
    'url': '/url',
    'method': 'POST',
    'data': {},
    'success': function(data){
        //success回调函数
    },
'error': function(data){
        //error回调函数
    }
});

事件的编程方式具有轻量级、松耦合、只关注事务点等优势,但是在多个异步任务的场景下,有些事件相互独立,有些事件相互依赖,如何协作是一个问题,在第4章关于流程控制以及事件协作的方法和技巧,我们会做进一步的探讨。

从前面的例子中,我们可以看到回调函数无处不在,这是因为在JavaScript中,函数是第一等公民,可以将函数作为对象传递给方法作为实参进行调用。但是回调函数这种编程方式对于很多习惯同步思维编程的人来说,也许是十分不习惯的。

1.3 单线程

JavaScript在浏览器中是单线程的,Node同样也保持了这个特点。单线程的好处就是不用像多线程那么处处在意状态的同步问题,没有死锁的存在,没有线程上下文切换的开销。但是单线程也有它自身的弱点,这些是Node开发者必须要重视的,我们需要在开发中积极地去面对这些弱点,这样可以享受到Node本身带来的优势,也能尽量避免它的弱点,使其可以高效利用。单线程的弱点有以下3方面:

(1)无法利用多核CPU

(2)错误会引起整个应用退出,应用的健壮性值得考验

(3)大量计算占用CPU导致无法继续调用异步I/O

这里,我们先来看看JavaScript大计算的场景,早期浏览器JavaScript和UI是共用一个线程的,JavaScript长时间执行会导致UI的渲染和响应被中断。在Node中,如果主线程的计算量很大,长时间占用CPU,也会导致后续的异步I/O调用发不出,已完成的异步I/O的回调函数也会得不到及时执行,那么就不能最大程度地发挥Node并行I/O的高性能。

后来HTML5定制了Web Worker的标准。Web Worker能够创建工作线程来进行计算,结算结果通过事件消息传递给主线程,主线程继续负责UI渲染,通过这样解决JavaScript大计算阻塞UI渲染的问题。

Node采用与Web Worker相同的思路来解决单进程中大计算量的问题:child_process,子线程的出现,意味着Node可以从容地应用单线程在健壮性和无法使用多核CPU方面的问题,它的思路是这样子的,由Master进程负责将计算分发给各个子进程,Master进程继续执行异步I/O调用,这样可以避免主线程陷入大计算而阻塞异步I/O调用的发起,子进程执行完的结果通过事件消息的方式传递给Master进程。通过Master/Worker的管理方式,也可以很好地通过Master进程管理各个工作进程,以达到更高的健壮性。

1.4 跨平台

起初,Node只可以在Linux平台运行,但随着Node的发展,微软注意到它的存在,并投入一个团队帮助Node实现Windows平台的兼容,在V0.6.0版本后,Node已经能够在Windows平台上运行了。图3是Node基于libuv实现跨平台的架构示意图。

图3 Node基于libuv实现跨平台的架构示意图

兼容Windows和*nix平台主要得益于Node架构的改动,主要是在操作系统和Node上层模块系统之间构建一层平台层架构,即libuv,关于libuv的设计,我们在第3章会介绍。

二、Node的应用场景

2.1 I/O密集型和CPU密集型

在技术选型之前,需要了解一项新技术适用于什么场景,关于Node,探讨的较多的主要是I/O密集型和CPU密集型。

Node擅长I/O密集型的应用场景基本上是没人反对的。Node面向网络且擅长并行I/O,能够有效地组织和利用硬件资源,从而提供更多好的服务。那么换一个角度,在CPU密集的应用场景中,Node是否能胜任呢?实际上,Node是足够高效的,它优秀的计算能力主要来自V8的深度性能优化。CPU密集型应用给Node带来的挑战主要是:由于Node单线程的原因,如果有长时间运行的计算,将会导致CPU时间片不能释放,使得后续的I/O异步调用无法发起。但是我们可以适当地调整和分解大型运算任务为多个小任务,使得运行计算任务能够适时释放CPU,不阻塞I/O的调用,这样就能享受到并行异步I/O的好处,又能充分利用CPU。

Node的异步I/O已经解决了在单线程中CPU和I/O阻塞无法重叠利用的问题。但是对于长时间运行的计算,如果它的耗时超过了普通阻塞I/O的耗时,那么应用场景要重新评估,因为这类场景是计算比I/O阻塞还影响效率,甚至说是一个纯计算没有I/O的场景,这类场景一般我们采用多线程的方式。Node虽然没有提供多线程用于计算支持,但是可以通过以下两种方式来充分利用CPU。

(1)Node可以通过编写C/C++扩展模块的方式更高效地利用CPU,将一些V8不能做到极致的地方通过C/C++来实现。

(2)如果单线程的Node不能满足性能需求,甚至用C/C++扩展还达不到要求,那么可以通过子进程的方式,将一部分Node进程当做常驻进程用于计算,利用进程间的消息传递结果,将计算与I/O分离,这样能充分利用多核CPU。

总的来说,CPU密集型对Node来说不可怕,如何合理调度是诀窍。

2.2 与遗留系统和平共处

有人会说,JavaScript现在实现前后端语言统一了,将来会不会干掉其他语言,朴灵的观点是不会,Node会与遗留系统和平共处。朴灵用两个案例充分验证自己的观点,第一个案例是LinkedIn移动版网站用Node重构的实践,旧有的系统具有非常稳定的数据接口,持续为传统网站服务,同时为移动版网站提供数据源,通过Node可以异步并行调用这些数据接口,而不用关心这些数据接口背后是什么语言实现的。第二个案例是国内的雪球财经,用Node来完成Web端的开发,同样利用Java作为后端接口和中间件。

三、Node的使用者

在短短的四年多的时间里,Node变得非常热门,使用者也非常多,这些使用者对于Node的倚重点各不相同。经过整理,主要有以下几类:

(1)前后端编程语言环境统一

(2)Node带来的高性能I/O用于实时应用

(3)并行I/O使得使用者可以更高效地利用分布式环境

(4)并行I/O,有效利用稳定接口Web渲染能力

(5)云计算平台提供Node支持

(6)游戏开发领域

(7)工具类应用

作者:MIG无线合作开发部实习生marcozhguo

电子邮箱:446882229@qq.com

参考资料:

《深入浅出Nodejs》

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏极客编程

为什么43%前端开发者想学Vue.js

根据JavaScript 2017前端库状况调查 Vue.js是开发者最想学的前端库。我在这里说明一下我为什么认为这也是和你一起通过使用Vue构建一个简单的Ap...

552
来自专栏Java帮帮-微信公众号-技术文章全总结

MQ消息中间件(工作+面试)

MQ消息中间件(工作+面试) ? AMQP协议介绍 AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协...

5417
来自专栏IMWeb前端团队

2015-2016前端架构体系技术精简版

2015-2016前端架构体系技术精简版 ? 点击查看github高清图 点击查看完整版 一、框架与组件 **bootstrap等UI框架设计与实现 伸缩布局...

1935
来自专栏杨建荣的学习笔记

自动化平台中的ORM和权限设计

最近在梳理平台里的一些基础架构和设计,力争把平台里的通用的部分能够抽象出来,迭代复用。 在数据库设计上我秉承了从简的原则,如果能用一个表搞定,我绝对不会把它拆分...

3135
来自专栏前端黑板报

HTTP2基础教程-读书笔记(三)

前面两篇记录了HTTP的历史和网络请求、页面渲染的过程以及HTTP/1的一些问题,本篇就来讲一下迁移HTTP/2需要考虑的一些问题。 迁移HTTP/2说简单无非...

3349
来自专栏程序猿DD

都在说微服务,那么微服务的反模式和陷阱是什么(三)

前文导读: 《都在说微服务,那么微服务的反模式和陷阱是什么(一)》 《都在说微服务,那么微服务的反模式和陷阱是什么(二)》 九、通信协议使用的陷阱 在微服务架构...

1695
来自专栏尜尜人物的专栏

分布式事务一致性解决方案

  所谓分布式服务,就是把之前通过本地接口交互的模块,拆分成单独的应用独立部署,并通过远程接口和网络消息交互。且不管这样说严不严密,正不正确,理解就好。本文的重...

572
来自专栏前端架构与工程

前后端分离和模块化-58到家微信首页重构之路

微信钱包内的58到家全新首页已经上线,感兴趣的同学们可以在微信中打开“我的->钱包->58到家”查看。 58到家全新首页提出重构主要是为了解决以下问题: 每个城...

1888
来自专栏liulun

riot.js教程【一】简介

题记 这是一个系列文章的第一篇 如果关注riot.js的人,可以关注我的博客; 我接下来会持续不断的发这一块的文章; 系列文章内容大多来自官网翻译; Riot...

2076
来自专栏一名合格java开发的自我修养

交易系统使用storm,在消息高可靠情况下,如何避免消息重复

概要:在使用storm分布式计算框架进行数据处理时,如何保证进入storm的消息的一定会被处理,且不会被重复处理。这个时候仅仅开启storm的ack机制并不能解...

713

扫码关注云+社区