Node.js学习——理论篇

本文已迁移至我的个人博客:http://ipenge.com/35934.html


前端时间学习了Node.js,重点看了《深入浅出Node.js》这本书,作为一个对javascript只有概念性认识的后端程序员,对Node多少有了一些自己的认识。将学习过程整理成两篇(理论篇、实战篇),方便与大家交流讨论。本文是第一篇——理论篇。

概念与源起

首先来看Node.js官网给出的对node的描述:

Node.js is a JavaScript runtime built on Chrome’s V8 Javascript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

从该描述中,我们可以找出几个关键词:

JavaScript runtime:表明node.js是一个javascript的运行时环境。

V8:node构建在V8引擎上。

event-driven:时间驱动。

non-blocking I/O:非阻塞I/O。

以上四个特性成就了Node.js的流行,使它一度成为了github上最受关注的开源项目。

Node.js诞生于2009年。2009年5月,Ryan Dahl 在GitHub上发布了Node.js的最初版本。仅仅两年后,2011年11月,Node超过Ruby on Rails,成为GitHub上关注度最高的项目。(目前排在第4位, https://github-ranking.com/)


Node.js学习——理论篇_第1张图片
Node.js作者Ryan Dahl


Node.js学习——理论篇_第2张图片
github关注度排行。2016.8.29

Javascript发展历史

任何一项新技术都不是凭空产生的,而是在一个时代的大背景下,通常是站在巨人的肩膀上,Node.js也不例外。由于node.js使用javascript作为开发语言,因此这里我们来简单回顾一下javascript的发展历程。


Node.js学习——理论篇_第3张图片
javascript发展历史

1990年,www(world wide web)诞生,这是整个互联网的基石。

1994年,盛极一时的网景公司成立,并发布了navigator浏览器,大获成功,迅速占领了整个浏览器市场90%的份额。

1995年,网景公司意识到,需要一种一种运行在浏览器端的脚本,由于当时的带宽非常有限,很多表单数据提交到服务器后,才发现表单填写错误,这对带宽是一种浪费,如果能够在客户提交请求之前就做好检查,则一方面可以提升用户体验,另一方面减少带宽占用以及服务器端压力。在这种背景下,他们开发了一种脚本语言,名叫Mocha。由于同年,Java语言也刚刚诞生并大获流行,为了更提高知名度,网景公司与sun公司达成协议,将Mocha更名为Javascript。

1997年,为了使javascript能够成为浏览器脚本语言的通行标准,网景公司将javascript提交到ECMA标准化组织,该组织发布了ECMAScript1.0版本,javascript正式成为一种规范。

2005年,Ajax技术诞生。异步的应用,大大提升了客户端浏览体验。

2008年,google发布了V8引擎,该引擎性能强劲,是javascript引擎中名副其实的王者,这也使得javascript的性能不再成为瓶颈,为javascript的大范围应用提供了基础。

2009年,CommonJS规范发布,定义了很多应用程序接口,如文件系统,IO等,从而填补了javascript在浏览器之外领域的空白。该规范希望javascript可以在任何地方执行。同年,Node.js诞生,可以看到,Node.js的成功虽然离不开其基于事件的、异步IO特性,但是也离不开v8引擎的性能驱动以及commonJS规范的理论基础。

2010年,npm包管理器发布,从此开发者可以更方便的发布js模块,极大的促进了javascript生态的繁荣。

2011年,Node.js项目成为github最受欢迎的项目。

应用场景

基于node的特性,它特别适合两类应用:

IO密集型

可以使用node来搭建RESTfull API,来响应客户端的高并发请求。典型的RESTfull API通常的逻辑是接受请求,解析参数,然后查询数据库或缓存,进行一些简单的业务处理,然后返回结果。其中数据库查询是比较耗时的IO操作,其他业务处理都是非常快的,因此这种场景就适合使用Node.js来处理。

数据密集型

大批量数据的处理场景,例如队列输入,实时仪表盘等。淘宝指数就曾使用node.js来搭建服务,另外还有linkedid的移动端服务。linkedin在2011 年将核心移动端服务从ruby迁移到node.js,负责人给出三点理由:

(1)更高的性能,在特定场景下Node.js能比Rails快20倍。

(2)使用3个服务器而不是30个就能应对10倍的流量增长。

(3)前端工程师能够进行后端代码的开发,两个团队实际上合二为一了。

同步or异步

同步与异步的区别在于消息通知机制:

同步:发起调用后,被调用者不会立即返回结果,而是直到执行完毕才返回。(主动获取结果)

异步:发起调用后,被调用者立即返回,但是真正的结果是在被调用者执行完毕后通过消息通知、回调等方式告知调用者。(被动等结果通知)

Node.js学习——理论篇_第4张图片
同步


Node.js学习——理论篇_第5张图片
异步

拿去餐厅吃包子为例,同步的方式是,点完包子后,服务员不会主动告诉你包子好没好,你要想能吃到包子,就得自己反复的询问:“我的包子好了吗?”,最终吃到包子前,可能需要经过多次轮询。而异步则是点了包子后,服务员给你一个号码牌,你自己不用去问,包子好了会喊号。

阻塞or非阻塞

阻塞与非阻塞的区别在于等待结果的过程中会不会被挂起:

阻塞:结果返回前,调用方挂起,不能执行其他任务。(挂起)

非阻塞:结果返回前,调用方无需挂起,可以执行其他任务。(不挂起)


Node.js学习——理论篇_第6张图片
阻塞


Node.js学习——理论篇_第7张图片
非阻塞

仍然拿吃包子为例,阻塞的方式是:店里只有一个厨师,你点了包子后,他就去后厨做了,你也不知道他什么时候能做好,只能一直等着。非阻塞的方式是:店里有一个服务员,你点了包子后,服务员告诉厨师做包子,你可以随时问包子好了没有,如果没好,你可以做别的事情,如果好了,你就可以拿走吃包子了。

单线程or多线程

单线程和多线程区别在于任务是不是连续执行,会不会被中断:

单线程:一个任务处理完后才处理下一个任务。(无任务切换)

异步:同时处理多个任务,每个任务执行一段时间然后切换到下一个任务。(任务不断切换)


Node.js学习——理论篇_第8张图片
单线程


Node.js学习——理论篇_第9张图片
多线程

单线程:大家排好队买包子,一个一个的点单,做好了张三的包子,再给李四点单。

多线程:只有有人买包子,就可以点单,如果张三和李四都点单了,就做一会张三的包子,做一会李四的包子,哪个包子做好了就给哪个。

Node的就餐体验

店里有一个服务员负责点单,每个人点单后可以领到一个号码牌,包子好了就叫号。后厨有多名厨师负责制作包子。类似于肯德基和麦当劳。

异步:点完包子后,领到号码牌,等着叫号

非阻塞:等待叫号期间,可以做别的事

单线程:前台给一个客户点完餐后,接着给下一个客户点餐。

Node.js学习——理论篇_第10张图片
node执行过程

Node.js架构


Node.js学习——理论篇_第11张图片
node架构

application/modules:我们的应用程序或者模块,由javascript写成。

c/c++ binding:打通js和c++通信的胶水。由于上层应用是javascript,而v8和底层实现由C++写成,因此需要胶水语言进行不同语言间的通信。

Addons:提供给开发者使用的开发c模块供上层js使用的工具。

libuv:屏蔽不同操作系统底层异步功能的实现。Window系统使用IOCP(Input/Output Completion Port)实现;Linux系统使用epoll系统调用实现。

其他组件:包括node实现的一些底层功能模块,包括文件系统、http等等,也包括开发者自己用c++实现的功能模块。

IOCP和epoll

IOCP(I/O Completion Port):IO完成端口,是一种windows中的异步IO模式。其原理大致如下(个人理解,如有错误,敬请指正):

Node.js学习——理论篇_第12张图片
IOCP原理

首先有一个主线程,用于不停的接受IO请求,然后将请求绑定到ICOP上,然后发起一个异步调用去执行IO操作。IO完成后,数据已经被写入到缓冲区,同时将完成信息放入IOCP中。IOCP虽然名为完成端口,但其实是一个类似队列的存在,用于存放已经完成的IO事件信息。另有一个工作线程池,每个线程不停的检查IOCP中是否有完成的IO操作,如果有的话,就从缓冲区中读取已经完成的数据信息,执行后续的数据处理操作。

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,其大致工作原理如下:

Node.js学习——理论篇_第13张图片
epoll工作原理

在Linux中,有关epoll有3个系统调用函数,分别是epoll_create,epoll_ctl,epoll_wait。

epoll_create用于初始化,会在内核中创建一个高速cache,其中保存了一颗红黑树,每一个节点是一个IO事件。

epoll_ctl:用于添加IO事件,会将新的IO事件添加到红黑树中,等待执行。每个IO事件被注册到系统中断中,当执行完毕后,就放入就绪队列。

epoll_wait:从就绪队列中检查是否有已完成的IO事件,有的话做下一步处理。

Node.js生态

一项新的技术,我们是否考虑在生产环境使用,除了它本身是否足够优秀外,还要考虑这些技术的生态,是否有好的技术支持,是否有活跃的技术社区。就像我们买房子,除了房子本身的质量外,我们还会考虑物业是否完善,周围的配套设施是否完备。

可以说,node具有了非常良好的生态,一方面,有node.js基金会来负责维护,会不定期发布新版本,修改上个版本的bug(有好物业);另一方面,有非常繁荣的开发者社区,github上有成千上万的node模块,提供的日常开发所用到的各种基础模块,例如socket编程,mvc,单元测试,运维等等,基础设施足够完备。

有了以上基础,我们有理由相信,node.js将可以在很多领域给我们带来非常出色的表现,我们也可以尝试去使用这项技术。

你可能感兴趣的:(Node.js学习——理论篇)