Node特性
传统的Web服务器端是多线程的,而Node是单线程的。
多线程就像去银行办理业务有多个窗口,但每个窗口一次只能接待一位客户,当窗口为客户办理业务时其他客户就只能等待。
Node的单线程就像去咖啡馆。一般只有一个收银台,顾客排队下单,店员拿到订单做好饮品后,会通知对应的客户。
- 单线程:
Node的运行时环境是基于V8引擎的。V8最大的特点是单线程,因此Node也是单线程的。
Node的单线程指的是主线程是“单线程”。所以不能有耗时很长的同步处理程序阻塞后面的执行,对于耗时任务,应该采用异步执行。
优点:
- 减少了创建、销毁和切换线程的开销,执行速度相对更快;
- 内存占用小;
- 更加安全。多线程容易出现死锁的问题。
缺点:
- 稳定性不足。线程出错会导致整个进程退出
- CPU利用率。单线程只能利用CPU的一个核,对于多核CPU的利用不足。
- 异步I/O
对于阻塞I/O,当需要执行I/O操作读取硬盘和网络等数据时,线程会被阻塞,直到拿到数据。
对于非阻塞I/O,在发起I/O处理后,不用等请求完成,可以继续做其他事情。
非阻塞I/O在准备好数据以后还是要阻塞进程去内核读取数据,因此不算异步I/O;
而异步I/O是不会造成任何阻塞
- 事件驱动
事件驱动就是通过监听事件的状态变化来做出相应的操作。如读取文件。
如果某个事件的回调函数是计算密集型(CPU被占用)函数,那么这个回调函数将会阻塞所有回调函数的执行。
Node优缺点
- 优点:
- 强大的并发能力。可以瞬间处理大量的客户请求
- 丰富的模块。
- 成熟的Web框架。
缺点:
- CUP阻塞。不适合做CPU密集型的操作
- 稳定性低。因为是单线程,可以通过PM2来解决
-
Node内存限制
默认情况下V8引擎限制了内存的使用(在64位系统下不超过1.4GB,32位系统下不超过0.7GB)。
process.memoryUsage()来检查。
使用node --max-old-space-size=200
或者node --max-old-space-size=200
(单位为MB),来指定堆内存中老生代和新生代的最大值,是静态方法。运行时不能更改。node内存泄漏:
缓存:如使用Lodash库的memorize来缓存函数参数和结果
- 重复的事件监听;
node内存分析工具:heapdump
Node事件循环
- 定时器(timer)阶段:执行setTimeout和setInterval调度的回调任务。
- 等待回调(pending callback)阶段:用于执行前一轮事件循环中被延迟到这一轮的I/O回调函数(处理网络、IO 等异常时的回调,有的 *niux 系统会等待发生错误的上报,所以得处理下。)
- 闲置、准备(idle,prepare)阶段:只能在Node事件内部使用。
- 轮询(poll)阶段:最重要的阶段,执行I/O事件回调,在适当的条件下Node会阻塞在这个阶段。
- 检查(check)阶段:执行setImmediate的回调任务。
- 关闭回调(close callback)阶段:执行close事件的回调任务,如套接字(socket)或句柄(handle)突然关闭
Node.js 对宏任务做了优先级划分,从高到低分别是 Timers、Pending、Poll、Check、Close 这 5 种,也对微任务做了划分,也就是 nextTick 的微任务和其他微任务。执行流程是先执行完当前优先级的一定数量的宏任务(剩下的留到下次循环),然后执行 process.nextTick 的微任务,再执行普通微任务,之后再执行下个优先级的一定数量的宏任务。。这样不断循环。其中还有一个 Idle/Prepare 阶段是给 Node.js 内部逻辑用的,不需要关心。
Node的多进程
创建多进程需要考虑的问题:
- 进程间的通信:进程之间如何发送消息、传递数据。
-
1. PM2
2. child_process
3. cluster
由于cluster模块是基于child_process封装的,所以在进程管理上两者存在一些相似的API和事件。
4. worker_threads
这个功能是专门用来处理CPU密集型任务的,在处理I/O任务时并不建议使用,因为可能会降低执行效率。
相比前面几个都优势: 操作系统切换线程比切换进程更快,开销更小。
- 线程之间可以共享内存。在处理大型数据的时候就变得非常有优势,比如父进程中有1GB的数据需要发送给两个子进程进行处理,那么由于内存不共享,每个子进程也需要使用1GB内存空间来存放数据,这样父子进程总共要占用3GB内存,而使用线程不需要额外创建。
- 线程共用父进程 ID,方便管理。比如遇到异常情况要批量停止这些线程时,只需要kill 掉父进程,这些子线程就会随之停止。