Node.js是一个JavaScript运行平台
- Node.js是基于Google Chrome的JavaScript运行时建立的平台,用于搭建响应速度快、易扩展的网络应用。
- Node.js实际上是JavaScript运行环境,对Google V8引擎进行了封装。
- Node.js使用事件驱动机制、非阻塞I/O模型,因此得以轻量和高效。
- Node.js适用于分布式设备上运行数据密集型的实时应用
Node.js所采用的GoogleV8引擎是基于ECMAScript2015开发的,ECMAScript2015是ECMAScript标准的第6个版本,又称为ES6,可简写为ES2015。
Node.js有什么优点呢?
Node.js和JavaScript的优势之一是它们的单线程编程模型,多个线程一般会引入bug,尽管一些新的编程语言,包括Go、Rust试图提供更加安全的并发工具,但Node.js仍然保留了JavaScript在浏览器中所用的模型。在为浏览器编写代码时,编写的指令序列依次执行一条,代码并不是并行执行的。然而对于用户界面来说,这样是不合理的。没有用户愿意在浏览器执行网络访问或文件获取这样低速操作时等待。为了解决这个问题,浏览器引入了事件机制:当点击按钮时,会有一个事件被触发,有一个之前定义的函数会运行起来。事件机制可以规避在线程编程时经常出现资源死锁和静态条件。
- 单线程
Node.js单线程是指Node并没有创建线程的能力,所以代码都是单线程执行的。不过Node宿主环境并不是单线程的,它维护一个执行队列,循环检测并调度JS线程来执行,因此单线程执行和并发操作并不冲突。 - 事件轮询机制
Node.js 可在不断新增额外线程的情况下,依然对任务进行并发处理。它是通过事件轮询(event loop)来实现并行操作的。 - 非阻塞I/O
由于Node.js是事件驱动的,因此使用了事件循环来解决I/O操作带来的瓶颈。在Node.js中一个I/O操作通常会带有 一个回调函数,当I/O操作完毕并返回时,会调用此回调函数。与此同时,主线程则继续执行接下来的代码。 - V8虚拟机
Node.js是一个基于Google Chrome V8 Javascript引擎之上的平台,可用于创建轻量级、快速、可扩展、事件驱动和非堵塞I/O的应用。 - 事件驱动
Node.js使用事件驱动模型,即当Web Server接收到请求时,将其关闭然后进行处理,然后去服务下一个Web请求。当请求完成后,被放回处理队列中。当到达队列开头时,结果被返回给客户端。 - RESTful API
支持Web服务和动态Web应用程序的多层架构,实现可重用性、可扩展性、组件可响应性的清晰分离。开发人员可轻松使用AJAX和RESTful Web服务创建丰富网络应用。
Node.js适用于那些场景呢?
- 面向服务的架构
面向服务的架构就是做号前后端的依赖分离,将所有业务的关键业务逻辑都封装成RESTful接口调用,上层只需考虑如何用接口来构建具体应用。这样后台程序员无需知道具体数据是如何从一个页面传递到另一个页面的,也无需知道用户数据更新是通过AJAX异步获取还是刷新页面获取的。
- RESTful API
RESTful API场景可处理数万条连接请求,该操作没有太复杂的逻辑,仅仅就是请求API,将数据进行返回即可。简而言之,其本质是从数据库中查找值并将其组成一个响应,由于这类响应是很小的文本,同时连接请求也是很小的文本,因此整体流量不高。
- AJAX请求应用
大数据时代对个人用户也面的定制信息已成为主流,当缓存失效后需发起AJAX请求,此时应用Node.js可响应大量的并发请求。Node.js适用于高并发、I/O密集、少量业务逻辑情况下的AJAX请求。
Node.js 不适用那些场景呢?
- 实时性要求很高的场景
例如工程交换机、工控机器人、DCS集控系统等。此类场景基本通过垃圾回收机制来管理系统内存,因此Node.js将会影响响应速度,并且难以优化。
- 计算密集恶性系统
计算密集型系统基本是C语言的天下,基于JavaScript语言的Node.js在计算性能上很难与C相比。
- 单一进程控制大内存的场景
由于Google V8引擎的设计原则,在32bit下有1G最大内存的限制,在64bit下有1.7GB的最大内存限制。虽然Node.js的Buffer分配可以不超过此限制,但也会带来垃圾回收机制上性能的退化。
Node异步机制的种类
Node异步机制大致分为回调函数、pub/sub
模式(事件模式)、异步库控制库(async
、when
...)、promise
项目、Generator项目等。
Node与V8
Node的动力源自Google V8 JavaScript引擎,是由服务于Google Chrome的Chromium项目组开发的,V8中一个值得称道的特性是它会被JavaScript直接编译为机器码,另外还有一些代码优化特性,所以Node才能这么快。
Node的本地部件libuv负责处理I/O,V8负责JavaScript代码的解释和执行。使用C++绑定层可将libev和V8结合。
非阻塞I/O
在服务器编程中访问磁盘和网络这样的I/O请求会比较慢,所以希望在读取文件或通过网络发送消息时,运行平台不会阻塞业务逻辑的执行。Node.js使用了三种技术来解决这个问题:事件、异步API、非阻塞I/O。
非阻塞I/O是一个底层术语,意思是说程序可以在做其它事情时发起一个请求来获取网络资源,当网络操作完成时,将会运行一个回调函数来处理这个操作结果。
例如:典型Node Web应用程序
使用Web应用库Express来处理商店的订单流程,为了购买商品,浏览器发起了一个请求,然后应用程序检查库存,为用户创建一个账号并发送回执邮件,同时会返回一个JSON格式的HTTP响应给浏览器。
这里同时在做的使用包括:
- 发送一件回执邮件
- 更新数据库来保存用户详细信息和订单
数据库是通过网络访问的,Node中的网络访问是非阻塞的,使用名为libuv的库访问操作系统的非阻塞网络调用。libuv库在Linux、MaxOS、Windows中的实现是不同的。
访问硬盘时比如在生成回执邮件并从硬盘中读取邮件模板时,libuv库会借助线程池模拟出一种使用非阻塞调用的假象。
在进行速度较慢的处理时让Node能做其它事情,是使用带非阻塞I/O的异步API真正的好处。即便只有一个单线程、单进程的Node Web应用,也可以同时处理上千个网络访客发起的连接。Node是如何做的,得先研究一下事件轮询。
事件轮询
例如:在典型的Node Web应用程序中,响应浏览器请求时,Node内置的HTTP服务器库即核心模块http.Server
使用流、事件、Node的HTTP请求解析器的组合来处理请求。在使用Express Web应用库添加的回调函数也是由它触发的。这个回调函数会触发数据库查询语言,最终应用程序会使用HTTP发送JSON作为响应。
整个过程使用了三种非阻塞网络调用:
- 一个用于请求
- 一个用于数据库
- 一个用于响应
Node是如何调配这些网络操作的呢?答案是事件轮询event loop
。
事件轮询是单项运行的先入先出队列,需要经历几个阶段,轮询中每个迭代都需要运行的重要阶段上图已展示。
- 首先计时器开始执行,计时器都是用JavaScript的setTimeout和setInterval函数安排好的。
- 接着是运行I/O回调,即触发提前编写的回调函数。
- 轮询阶段会去获取新的I/O事件
- 最后使用setImmediate安排回调
本例是个特例,因为允许将回调安排在当前队列中的I/O回调完成之后立即执行