• nodejs是什么?
  • nodejs的架构模式以及优缺点
  • nodejs异步IO
  • nodejs事件驱动
  • nodejs单线程
  • nodejs应用场景

一、nodejs是什么?

1.1nodejs是一个开源的、跨平台的JavaScript的运行环境。就像java的运行环境JRE一样,比如JRE自带的java基础类库,在nodejs中也提供了一系列的JavaScript基础模块。

就nodejs应用阶段的学习我们可以通过学习这些基础模块的API应用,理解nodejs的架构模式以及这个架构模式的优缺点,通过对基础模块的应用深入理解nodejs的特性:异步IO、事件驱动、单线程,然后弄清楚在什么场景下可以使用nodejs。

然后就是nodejs与浏览器中的JavaScript的区别

-- 在浏览器中JS大部分时间都是与DOM或Web平台的其他API(如H5的一些API)进行交互,在node中就不存在document、window和web平台的其他API。

-- 在浏览器中没有nodejs提供的模块机制友好的API,比如文件系统访问功能。

-- 最大的不同是使用nodejs你可以控制运行环境,而浏览器环境你是没办法选择的,所以一般情况下你并不需要做类型浏览器那样的兼容操作。除非你的项目是开源的要求其可以部署在任何nodejs环境中,比如像NPM、CLI这类工具。

-- 在模块系统上nodejs使用了CommonJS模块系统,而浏览器正在实施ES Modules标准。

1.2nodejs的架构模式:

-- Natives modules层:翻译为本机模块层,从直译角度不是很好理解,简单的理解就是它是nodejs这个JS运行环境的JS本身语言层,也就是说这一层的内容由JS实现。提供JS程序可以直接调用的库,例如fs、path、http等模块。

-- Bindings层:翻译为绑定层,这个从直译角度就非常好理解了,它就是绑定Natives modules的JS语言层和nodejs底层调用硬件的内核层的中间胶水层。因为JS无法直接调用底层硬件资源,如果JS需要与底层硬件设施进行通信,这需要一个桥梁,这个桥梁就是Bindings层。通过这个桥梁就可以让nodejs的核心模块获取到具体的服务支持,从而实现底层操作,比如:文件的读写行为。这一层模块基本上都是使用C/C++实现的,但它们并不提供具体的功能,更像是功能调用的对照表。比如Natives modules成的js代码需要调用一个A功能,而这个A功能的最终实现是基于C/C++实现的C功能,且C功能被放在另外一个地方,这时候A功能就需要去找到这个C功能,Bindings就是去帮助A找到C的这样一个对照表。

-- 底层模块:v8、libuv、c-ares(DNS)、http parser、zlib compressior等

v8:(JS内核)负责执行JS代码,提供桥梁接口。比如在JS调用了某个函数,而这个函数的具体功能最终是由C/C++实现的,这中间的具体调用和实现就需要v8来转换。v8为nodejs提供基础的初识化操作,创建执行上下文环境和作用域这些功能,nodejs有了v8就实现了JS的转换和调用底层的能力。

libuv库:nodejs中的事件循环、事件队列、异步IO底层都是基于这个库实现的。

-- 第三方功能库:c-ares(DNS)、http、zlib等都是具体的第三方功能。

-- 硬件资源和操作系统层:CPU/RAM/DISK OS

1.3nodejs中JavaScript组成

在nodejs中的javascript是由ECMAScript和nodejs扩展的模块共同组成,当然扩展的包括nodejs內置的模块也包含我们编写的JS代码、以及引入的第三方JS依赖模块组成。

这里需要注意的nodejs中的是基于ES5的规范,如果需要使用ES6的规范需要使用babel或其他转换工具实现。

1.3nodejs的优缺点:

在讨论具体的优缺点之前,我们先来基于B/S架构模型来讨论同步、异步、单线程、多线程对架构的性能的影响:

假设现在有5个客户端请求同时发起请求,如果服务采用单线程处理这些请求,那么总时长就是(业务逻辑处理+IO处理)*5的时间之和。在现代服务器上这种单线程处理已经几乎不存在,从多线程的角度来说,假设现在服务器同步支持5个线程同时处理IO,那么总时长最糟糕的可能时长是(业务逻辑*5)+(处理最长时间的IO*5),并且如过处理最长时间的IO是第一个IO,这时候其他4个请求都需要等待第一个IO处理完毕,这种情况就会导致大量的请求被拥塞,导致客户端出现大量的客户端请求超时。

假设现在服务异步支持5个线程同时处理IO,总时长就是(业务逻辑*5)+处理最长时间的IO,并且可以使用已经处理完毕的请求继续处理后面的新请求,解决了请求被拥塞的问题。

所以从上面对B/S架构的分析来看,服务采用异步/IO多线程的策略就是最优的方案。这种模式主要就是解决了服务浪费时间等待IO处理的问题,这样的模式避免服务进行线程切换上下文的时候需要做的状态保存、时间消耗(IO时间)、状态锁等操作,这个模式也通常被叫做应答者模式 —— Reactor模式。

而nodejs基于v8内核本身就支持异步IO/多线程的Reactor模式,虽然这是nodejs被大量应用的原因,但并不代表nodejs就没有缺点,那么接下来就简单的基于nodejs的特性分析nodejs的优缺点。

nodejs区别传统服务的特点:异步事件驱动、非堵塞IO、单线程。通过下一节的内容来逐一分析这些特性,深入的了解nodejs的优缺点。

二、nodejs的特点及适用的应用场景

2.1异步IO

IO操作分为两类:阻塞IO、非阻塞IO,这种区分实际上并不在IO操作的本身,而是在调用IO操作的主进程上才能体现出来。因为多核CPU就支持多进程的IO操作,这种操作进程在相互之间并不会产生冲突,而是服务上调用系统IO操作的主线程是如何处理不同进程的IO操作。我们都知道每个IO操作进程会因为当前操作的资源大小需要不同的操作时间,这时候服务上的主线程是按照调用IO操作的现后顺序在原地逐个等待IO操作的返回结果;还是将IO操作启动后丢给一个管理机制,由这个管理机制去监听IO操作的返回结果,然后当这一机制监听到了IO操作的返回结果再通知主线程来处理。

显然,在nodejs中选择了后者,管理IO操作的机制被称为事件轮询,在后面的事件驱动架构中会就这个机制做具体分析。这里只需要大概的了解nodejs中的是基于主线程的事件轮询机制,实现的异步非阻塞IO,大概的实现架构逻辑参考下图:

异步IO逻辑流程图解析

-- node代码:调用IO异步事件任务API;

-- event loop(事件轮询):基于事件路由分解器和事件队列,循环监听事件(IO请求/响应);当事件多路分解器中有空闲的事件处理资源,并且事件队列中有未处理的对应事件请求,event loop将对应的事件请求分配给事件多路分解器处理;当事件队列中有已经处理的事件将其处理结果交给主线程处理。并且当事件队列为空时会停止轮询。

-- event demultiplexer(事件多路分解器):负责监听系统上提供的硬件资源,并调用这些资源的系统API处理当前事件,然后将处理结果添加到事件队列中。

-- Event Queue(事件队列):负责缓存当前正在处理的事件对象,是事件轮询的资源。

-- 操作系统层IO接口:网络资源接口、文件资源接口等。

常见的轮询技术:read、select、poll、kqueue、event ports。

在nodejs的架构模式中底层模块有一个libuv这个组件库,也是事件多路分解器的底层,当在nodejs中发起一个事件通过事件轮询将IO任务传递给事件多路分解器,多路分解器会根据事件类型匹配相对应的libuv组件去处理IO任务,通过下面的libuv组件库示图了解都有哪些组件:(关于这个组件库更多详细内容可以参考官网:http://libuv.org/

最后总结一下nodejs异步IO的优缺点:在高并发程序中异步IO肯定是nodejs的优势,反之不能说是缺点而是同步异步都无所谓,根据项目的实际应用场景而定,如果在非必要的情况下使用nodejs的异步IO导致项目架构变得更复杂那肯定是劣势。

2.2事件驱动

首先了解一下事件驱动的概念:所谓事件驱动是指在持续事件任务管理过程中,进行任务处理决策的一种策略,即跟随当前程序上出现的事件,调用可用资源处理相关任务,使得随着程序的运行持续出现的任务得以处理,防止任务堆积。

nodejs中的事件驱动机制是基于JS内核v8的异步编程作为基础实现,所以他与浏览器的事件机制除了在API上有一些差异,内部机制完全一致。其优点就是可以充分利用系统资源,实现执行过程中无需阻塞等待某个事件完成再往下执行。同样其优点也是其缺点,异步编程的风格会导致某些功能的代码过于分散、回调地狱等问题导致代码编写和阅读的体验不是很好;其二,既然事件采用异步模式就必然需要消耗资源去做监听和响应,但这相对于同步阻塞来说或许并不是缺点。

这里先简单的介绍一下nodejs的事件驱动机制的概念和优缺点,后面再在具体的nodejs模块分析中对事件API做具体的应用分析。

2.3单线程

nodejs的单线程机制还是基于JS内核v8的单线程机制,nodejs单线机制不是说nodejs就是单线程,而是说nodejs的主线程是采用单线程机制,然后配合异步事件多线程协作处理各种任务。

nodejs单线程的优点:

-- 不用像多线程编程那样,处处注意状态的同步问题,不会出现死锁的情况,也没有线程上下文交换所带来的性能上的开销。

nodejs单线程的缺点:

-- 无法利用多核CPU。

-- 错误会导致整个程序退出,应用的健壮性值得考验。

-- 如果有CPU密集型任务会导致异步I/O任务无法继续调用。

当下的nodejs基于的v8单线程机制是在最开始的时候Google公司为了解决JavaScript与UI共用一个线程出现长时间执行会导致UI渲染和响应会被中断,长时间的CUP占用也会导致异步I/O发不出调用,以完成的异步I/O回调函数也会得不到及时的处理。然后Google公司开发了Gears启用一个完全独立的进程,将计算量通过事件机制分发到其他进程,以此来降低运算造成的阻塞机率。后来HTML5制定了Web Workers的标准,Google放弃了Gears,全力支持Web Workers。Web Workers能够创建线程来进行计算,以此来解决JavaScript大计算阻塞的问题,工作线程不阻塞主线程,通过消息传递方式来传递运行结果,这也使得工作线程不能访问主线程中的UI。

nodejs中同样采用Web Workers创建线程的方式来解决单线程大量计算的问题:Child_process,子线程的出现既提高了单线程的健壮性,又解决了单程无法使用多核CPU的问题。关于如何通过子进程充分利用硬件资源和提升应用的健壮性,后面会有单独博客进行分析介绍。

2.4nodejs的适用应用场景

-- 非阻塞I/O特性使得nodejs适用I/O密集型高并发请求场景。基于这样的应用场景,很多高并发项目就基于nodejs在客户端和后端之间搭建了一个BFF层。BFF层主要用于处理网络请求、格式化数据、渲染HTML界面、合并接口、解决跨域需求、数据缓存。

-- 如果将nodejs看作是后端语言,项目不存在大量的业务逻辑的情况下,同样可以使用nodejs操作数据库,搭建一个轻量的高效的API服务,比如时实的聊天程序。

-- 前端工程化:nodejs本身基于JavaScript的平台,对于前端工程师来说是非常熟悉语言搭建的平台,并且还是基于浏览器的v8内核,应用nodejs在前端工程化中构建各种工具提高开发效率和协同工作具有非常大的价值。比如模块化构建的webpack、基于工作流的自动化构建工具glup、npm包管理器、还有各种各样的手脚架工具等。

2.5在特殊的应用场景下nodejs的适应性和问题如何解决

-- nodejs真的不擅长CPU密集型业务吗?

这个问题在前面的单线程特性里说过,因为密集型任务会导致异步I/O任务无法继续调用,这是一个nodejs的缺点,但是在一些项目需求中个方面都与nodejs的优点相契合,但在一些特殊的业务上会出现CUP密集型业务,这时候我们会非常纠结到底要不要使用nodejs,针对这一问题我们先来看看基于斐波那契数列算法实现n=40的计算各种语言的性能指数:

通过上面的测试指数可以显然的看到nodejs在性能上拥有不俗的表现,那么现在问题了,如我们一开始说的某些特殊业务可能出现CPU密集型业务,比如:长时运行的计算导致CPU事件片段不能释放,使得后续I/O无法发起。这种情况可以考虑将这个长时运算的大型运算任务分解成多个小任务,使得运算能适时的释放,不阻塞I/O的调用发起,这样就可以同时享受到并行异步I/O的好处,又能充分的利用CPU。

关于长时运算的分解方案有两种:

nodejs可以通过编写C/C++扩展的方式高效的利用CPU,将V8不能做到的性能极致通过C/C++来实现,在相关资料中这样的测试结论“通过C/C++扩展方式实现的斐波那契数列计算比Java还要快”。

如果上面的方案还不能达到要求,还可以通过子进程的方式,将一部分nodejs进程当作常驻服务进程用于计算,然后通过进程间的消息来传递结果,将计算与I/O分离,这样还可以充分的利用多核CPU。

-- nodejs如何与遗留系统和平共处?

过去大多都是同步的方式编写程序,这种串行调用下层应用数据的过程中充斥着串行的等待事件,如果采用多线程来解决这种串行等待有会显得小题大作。在这种情况或许会选择对系统进行彻底的重构,但这显然是一个非常大的工程。如果此时系统依然能稳定的提供数据输出,持续为网站服务,同时还能为移动端项目提供数据源。这时候或许可以考虑使用nodejs将该数据源当作数据接口,发挥nodejs的异步并行优势,而不用关心背后是用什么语言实现。

还有就是在一些JAVA项目中,为了避免java烦锁的表达,可以考虑使用nodejs替代java/jsp来完成web端的开发,使得前端工程师在HTTP协议的两端高效灵活的开发。这既利用了java作为后端和中间件的稳定性,又提高了开发中的协同合作效率。

-- nodejs的分布式应用:

分布式应用意味者可伸缩性的要求非常高,数据平台通常在一个数据集群中去寻找需要的数据。这时候可以考虑基于nodejs的中间层应用,这样可以利用nodejs实现高效的并行I/O,也同时实现了高效使用数据库。对于nodejs而言,这个只是一个普通的I/O,但对于数据库而言,却是一次复杂的计算,所以也是充分压榨硬件资源的过程,实际的应用案例有NodeFox、ITier。

nodeJs入门的第一节课的更多相关文章

  1. 0068 Git入门的第一节课

    这是 猴子都懂的Git入门 的学习笔记 Git安装与配置 下载安装Git:http://git-scm.com/ 从开始菜单启动Git Bash $ git --version git version ...

  2. [iOS]Objective-C 第一节课

    Objective-C 第一节课 本节课的主要内容 创建Objective-C的第一个工程 HelloWorld Objective-C中的字符串 创建Objective-C的第一个工程 打开Xcod ...

  3. centos mysql 实战 第一节课 安全加固 mysql安装

    centos mysql  实战  第一节课   安全加固  mysql安装 percona名字的由来=consultation 顾问+performance 性能=per  con  a mysql ...

  4. Centos安装自定义布局才能自己划分各个区的大小ctrl+z ,fg ,route -n ,cat !$ ,!cat ,XShell 设置, ifconfig CentOS远程连接 Linux中的输入流 第一节课

    Centos安装自定义布局才能自己划分各个区的大小ctrl+z ,fg ,route -n ,cat !$ ,!cat ,XShell 设置, ifconfig  CentOS远程连接  Linux中 ...

  5. Java第一节课动手动脑

    在第一节课的动手动脑中,主要解决四则运算问题. 首先第一个是出30道四则运算题目,在100以内.这个问题需要控制随机数生成的范围和结果的范围在100以内就可以. 第一次改进是3点:一为避免重复,二为定 ...

  6. 左神算法第一节课:复杂度、排序(冒泡、选择、插入、归并)、小和问题和逆序对问题、对数器和递归(Master公式)

    第一节课 复杂度 排序(冒泡.选择.插入.归并) 小和问题和逆序对问题 对数器 递归 1.  复杂度 认识时间复杂度常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数 ...

  7. 初学Python——第一节课

    一.Python语言的特性: 1.与C语言不同,Python语言是一门解释性语言.程序在执行过程中,执行一步.编译一步. 2.Python是一个动态类型语言,不需要定义变量的数据类型. 3.Pytho ...

  8. JAVAWEB第一节课的课后思考

    第一开发一个网站需要的一些技术 至少熟悉一种建站程序.(html,javascript等等)对空间和域名的知识有一定的了解.有一些美工基础(例如ps设计等等).对编程有一些了解.HTML的代码知识基本 ...

  9. springboot的第一节课

    快速开始spring boot应用 官方向导搭建boot应用 地址:http://start.spring.io/ 设置项目属性: 3.解压,拷贝到工作空间,导入maven项目 4.写Controll ...

随机推荐

  1. Yaconf-配置管理扩展

    1.下载yaconf安装包git clone https://github.com/laruence/yaconf.git2.目录切换至yaconf,编译生成so 文件(找到你的phpize位置) / ...

  2. LGP3126题解

    这道题还有点意思. 路径要求是一个回文串,回文串立马枚举中点.中点只可能在对角线上. 枚举对角线上的一个点,然后两边的路径必须完全相同. 既然路径上的字符必须完全相同,那么每个前缀也必须完全相同. 考 ...

  3. WPF之转换器

    IValueConverter的用法1. COnvert方法object Convert( object value, Type targetType, object parameter, Cultu ...

  4. ## [湖南省赛2019]Findme ###

    [湖南省赛2019]Findme 1.题目概述 2.解题过程 010打开这几张图片 先简单分析一下这几张图片 简单分析 1.png 从外观上,1.png明显高度太低,需要更改 2.png 2.png末 ...

  5. org.junit.platform.commons.JUnitException: TestEngine with ID 'junit-jupiter' failed to discover tests

    https://blog.csdn.net/qq_43349162/article/details/115285780 https://blog.csdn.net/Think_and_work/art ...

  6. kali下对Docker的详细安装

    镜像下载.域名解析.时间同步请点击 阿里云开源镜像站 前言 Docker是渗透测试中必学不可的一个容器工具,在其中,我们能够快速创建.运行.测试以及部署应用程序.如,我们对一些漏洞进行本地复现时,可以 ...

  7. vue路由-router

    VueRouter基础 vue路由的注册 导入 <script src="https://unpkg.com/vue-router/dist/vue-router.js"&g ...

  8. [SPDK/NVMe存储技术分析]003 - NVMeDirect论文

    说明: 之所以要翻译这篇论文,是因为参考此论文可以很好地理解SPDK/NVMe的设计思想. NVMeDirect: A User-space I/O Framework for Application ...

  9. sql语言:如何查询字符串某个字符的个数?

    sql语言:如何查询字符串某个字符的个数? 这语句太精彩了! select len('05011045')-len(replace('05011045','0',''))

  10. 第3 章 802.11 MAC

    一 前言 802.11 规格的关键在于MAC(介质访问控制层),属于数据链路层,它定义了数据帧怎样在介质上进行传输.MAC 位于各种物理层之上,控制数据的传输.不同的物理层可以提供不同的传输速度,不过 ...