nodejs
- Non-Blocking I/O Model
- Event Loop
- Event-Driven
- 基本架构
- 何为阻塞
- 代码执行时
- 阻止事件循环的几个维度
- Worker Pool
- npm模块的风险
Non-Blocking I/O Model
non-blocking是指node.js进程中不同步等待执行非javascript操作
(例如I/O)完成而继续执行下一块代码的特性。
注:CPU密集型属于javascript操作。
I/O通常指与磁盘
与网络
的交互
非阻塞I/O模型使得nodejs支持高并发且非常适合于I/O密集型应用
Nodejs Event Loop and Worker Pool
共6个阶段
timers
setTimeout与setInterval回调函数队列pending callbacks
会在下一次loop中执行的系统级回调队列。如TCP ECONNREFUSED -idle,prepare
内部使用poll
接收新的I/O事件。执行I/O相关回调。在这个阶段node进程可能会阻塞check
setImmediate回调会在这个阶段执行close
一些关闭的回调。比如connect.on('close', () => {....})
注:process.nextTick不属于任何一个阶段,它是介于任意两个阶段之间,并且在阶段切换时执行nextTick回调
Event-Driven
基本架构
- nodejs事件驱动架构中有两种线程:事件循环线程(Event Loop)以及工作线程池(worker pool)
- Event Loop负责编排客户端请求而后调度Worker Pool处理CPU密集型任务
注:因此nodejs并不是纯粹的单线程语言!
何为阻塞
- 如果Event Loop执行回调或worker执行任务需要很长时间,即为阻塞。当发生阻塞时,主要会有两点需要考虑:
- 性能:如果某worker线程定期执行heavyweight任务,会影响服务吞吐量(请求/秒)
- 安全性:假设某些输入会引起程序阻塞,则存在被恶意客户端利用并攻击的风险。即拒绝服务攻击。
代码执行时
- 在Event Loop中
同步
执行常规的变量、方法的定义与调用,javascript所有回调以及非阻塞异步I/O如网络I/O - Worker Pool是libuv(线程池工作调度的c++库)在Worker Pool中
异步
执行“昂贵”繁重的任务。node提供非阻塞I/O(操作系统不提供)API,以及CPU密集的I/O API- I/O密集型API:
- DNS: dns.lookup()
- fs: fs.readFile(),除了那些显示说明同步的方法
- CPU密集型API:
- crypto: crypto.pbkdf2()
- zlib: 除了那些显示说明同步的方法
- I/O密集型API:
Event Loop实质
抽象来说,Event Loop维护挂起事件的队列,Worker Pool维护挂起任务的队列。
实际上,Event Loop并不是维护一个队列。而是一个文件描述符的集合
,这些文件描述符从系统级事件通知机制获取比如epoll(linux),kqueue(OSX),IOCP(Windows)。这些文件描述符对应于某些网络套接字以及node正在监视的文件等等。当某个描述符准备好时,Event Loop会将其转换为合适的事件并执行对应的回调。
另外,Worker Pool维护的是一个真正的队列。Worker会pop出队列的task并执行,完成后会触发Event Loop“至少一个事件已完成”的事件。
阻止事件循环的几个维度
- 数据处理流程中是否包含计算复杂度高的任务,比如使用CPU密集型Node API比如crypto,fs,zlib,child-process(分区处理与offload to Worker Pool)
- ReDoS攻击,检查是否存在易受攻击的正则表达式(使用安全正则表达式库做安全校验)
- 是否在主线程中使用JSON.parse以及JSON.stringify(潜在风险,因此也建议offload给Worker Pool)
Worker Pool
nodejs默认的Worker Pool专门用于处理I/O任务,维护自己的线程池可以使用cluster模块以及child_process模块做自定义线程池。
Node服务器的吞吐量取决于
WorkerPool的吞吐量。有效降低逐个任务时间开销
以及稳定任务时间开销的变化
将最大程度提升服务器的吞吐量。最常见的方法就是复杂重复型任务(比如数组迭代)做分区处理。
注:由于调度Worker Pool会增加额外的通信开销,因为Worker Pool无法获取主线程的命名空间从而无法直接读取Javascript对象,所以需要序列化/反序列化导致增加通信成本。
npm模块的风险
npm生态系统中存在数十万个模块为开发者提供了极大的便利,然而社区中npm包良莠不齐,因为无法较为准确的估计其使用Event Loop或者Worker Pool的成本而导致一些程序隐患。