node最近被唱衰,尤其是在后端领域,node确实有不适用的地方,但无脑黑的真不少,随即想再复盘整理下node相关的资料,也随手来个使命感(口号)“保护我方输出 nodejs”
nodejs概述
nodejs是什么
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。它允许通过JavaScript和一系列模块来编写服务器端应用和网络相关的应用。核心模块包括文件系统I/O、网络(HTTP、TCP、UDP、DNS、TLS/SSL等)、二进制数据流、加密算法、数据流等等。
运行时包含了程序运行的基本环境,这里有动态内存—堆,有线程栈,有最基本的运行时函数库,例如文件读写API, 网络交互,等等。
任何语言都需要有自己的运行时,一直以来,javascript 都以浏览器为运行时,从事客户端的工作(网络、DOM、外部事件、HTML5视频、canvas和存储),node的出现突破了这一限制,为javascript在服务器端开疆拓土提供了环境。
nodejs 架构
Node.js主要分为四大部分,Node Standard Library,Node Bindings,V8,Libuv等,架构如下
- 标准库是node直接提供给开发者的接口
- node bindings 提供js和c/c++交互的通道,封装了v8和libuv,向标准库提供基础接口
- v8是谷歌开发的js引擎,提供js运行环境
- libuv 是一个跨平台的异步I/O库
- c-ares 是异步处理dns的库
- http_parser等提供http解析,ssl,数据压缩等能力
nodejs的特性
事件驱动,非阻塞io
正如node架构展示的那样,node拥有libuv非阻塞io,事件驱动的能力,擅长io密集型任务;
事件驱动是说通过注册事件队列,轮询处理事件的机制,如图
轮询执行的简化概述如下
1. 定时器:处理已经被setTimeOut()和setInterval() 调度的回调函数
2. 待定回调:执行延迟到下一个循环迭代的I/O回调
3. idle,prepare:系统内部使用
4. 轮询:检索新的I/O事件,执行与I/O相关的回调
5. 检测:执行setImmediate()回调函
6. 关闭回调函数:一些关闭的回调函数
非阻塞io是说基于epoll 事件驱动的多路复用IO / IOPC异步IO 的IO处理模型
单线程
单线程是说,v8单线程解释执行js的特性, 因此node本身不擅长处理cpu密集型任务,但可以通过cluster模块的多进程模型来适当解决;
基于node特性的发散和延展
由node特性可以看到,一个node程序运行后,会产生两种线程:主线程即事件循环线程;线程池中的工作线程。这种特点决定了,主线程阻塞或者线程池的线程阻塞耗尽,都将成为node应用性能瓶颈,对于生产应用来说就是安全故障隐患,因此应该尽可能降低每个回调任务的复杂度,减少耗时操作(复杂正则,复杂json处理,密集计算)
常见耗时任务
-
复杂正则
通过正则复杂度模块 检查确定是否是复杂正则 safe-regex
-
node同步api
node的异步IO是特色,但也提供了方便使用的同步IO api,但是这些同步api是高开销的,在服务端应谨慎使用。主要包含以下模块:
- crypto
- zlib
- fs
- child_process
-
复杂json解析
JSON.stringify() 和 JSON.parse()包含了高耗时操作,对复杂的json解析,耗能严重,应考虑对交互json深度进行限制或者使用异步流api,如Big-Friendly JSON
耗时任务的处理方式
对于“大”问题,一般解决思路都是分治,一大化多小,拆分思路如下:
-
拆分任务
在同一线程内进行任务拆分,比如遍历任意长数组的元素,可通过闭包的方式保存上下文,分段执行
-
分流处理
通过cluster模块,子进程,外部负载均衡进行分流
通过N-API 主动为node线程池分配任务
定时器
-
setTimeout / setInterval
setTimeout / setInterval 返回Timeout对象, 可指定回调任务间隔多久后执行,调度的回调在事件轮询的timers阶段执行
-
clearTimeout / clearInterval / unref
clearTimeout / clearInterval 取消Timeout对象调度回调函数的执行
unref() 将定时器对象标进行active–,当active 为0时,定时器退出,定时器调度的回调函数则不会执行
-
setImmediate
setImmediate调度回调函数 在当前时间轮询的任何I/O操作后,且在下一轮定时器执行前 执行
process.nextTick()
process.nextTick() 不属于事件轮询,它总是在当前操作完成后被处理,不管事件循环进行到哪个阶段
node市场定位和前景
从09年node发布第一个版本到现在,已经过去十年了,node从“威胁”java地位的高性能后端语言,逐步过渡到了前端工程化的工具语言,无论是开发者还是企业技术团队,通过用脚投票的方式基本摒弃了node在后端的使用价值,至少到现在,node的市场定位就落在了大前端方向
前端如今的繁荣离不开node的发展,在一段时间内,node将依然在前端工程化,前端业务逻辑复杂化,serverless等领域建功立业,对于前端的同学来说,node已经从加分项慢慢变成必备技能