Node.js 事件循环

214 阅读4分钟
事件循环

5CABD73C-099A-496F-B184-CD999F9095FC.png

事件循环模型(黑色方块组代表该阶段队列,图片来源于互联网)

Node.js 使用Reactor 模式来运行,事件循环如上图所示,具体步骤如下:

  1. Node.js检查 Next tick 队列Other Microtasks 队列,如果这两个队列有任何事件可执行,则Node.js便会立即开始处理它们,直到两个队列被清空。一旦他们被清空,事件循环将继续到下一个阶段。
  2. Node.js 检查并执行定时器队列Expired Timer queue)中的任何过期定时器来启动事件循环,这里的“队列”是指最小堆。在此阶段(执行事件循环中的每个队列可以被视为事件循环的一个阶段)后,在此阶段后,仍会检查并执行 Next tick 队列Other Microtasks队列
  3. 接下来Node.js 检查并执行 I/O 事件队列I/O events queue)中的可执行事件。在此阶段后,仍会检查并执行 Next tick 队列Other Microtasks队列
  4. 如果其他队列中没有事件,则等待挂起的 I/O 去complete。
  5. 接下来Node.js 检查并执行立即队列Immediates queue)中的可执行事件。在此阶段后,仍会检查并执行 Next tick 队列Other Microtasks队列
  6. 接下来Node.js 检查并执行关闭处理程序队列close Handlers)中的可执行事件。在此阶段后,仍会检查并执行 Next tick 队列Other Microtasks队列
  7. 如果在任何队列中没有要处理的事件或Event multiplexer没有任何等待的请求,则退出循环。否则继续从第一步开始循环。
关键词
  • 反应器模式 (Reactor_pattern):是一种事件设计模式,用于处理由一个或多个输入并发传送给服务处理程序的服务请求。当请求抵达后服务处理程序使用解多路分配策略,然后同步的派发这些请求至相关的请求处理程序。
  • Event multiplexer:事件解多路器,它是由Libuv提取的I/O处理API的集合,并将该集合暴露给Node.js底层。
  •  I/OInput/Ouput 的缩写,也可以写成 IO,指输入和输出,比较有代表性的是流的读(输出)和写(输入)操作。
  • 事件:特定动作的已注册的回调函数,如打开文件系统可写流的文件及准备就绪的回调被称为“open”事件和“ready”事件,定时器/延时器/immediates的回调函数也可被看作为事件。
  • 事件队列:存放事件的某种数据结构,可能是队列、堆等。
  • Libuv:为Node.js编写的跨平台支持库,它围绕事件驱动的异步I/O模型进行设计。该库提供的不仅仅是对不同I/O轮询机制的简单抽象,’handles’和’streams’ 提供了对套接字和其他实体的高级抽象,还提供了跨平台的文件I/O和线程功能。
  • 阶段:执行事件循环中的每个队列可以被视为事件循环的一个阶段。
  • IO饥饿:事件循环始终处理Next tick队列,而不向前移动进入下一个阶段。在Node.js v0.12前可通过process.maxTickDepth设置队列的最大长度,现今暂无api进行硬性限制。
  • Node.js 高并发:高并发是指在同一时间服务器支持处理大量的用户请求服务。尽管Node.js的应用程序是单线程运行的,但使用Reactor patternLibuv的跨平台异步I/O,可以做到同一时间大量请求并行传递且有序处理,但若要实现真正的系统性的高并发,需要数据库操作(连接数,机器性能等)、缓存服务、消息队列、网络IO、文件读写等均实现高并发,而这往往需要借助不俗的计算能力,Node.js 怕是很难做到。因此 Node.js 更适合高并发弱计算的场景,如BFF
注释
  • 队列类型:
    • 定时器队列:通过setTimeout和setInterval添加的过期的定时器的回调。
    • I/O 事件队列:完成的 I/O 事件。
    • 立即队列:使用setImmediate 函数添加的回调。
    • 关闭处理程序队列:任何 close 事件。
    • Next tick 队列:使用process.nextTick 函数添加的回调,比Other Microtasks队列具有更高的优先级。
    • Other Mircotasks 队列:包括其他microtask,如promise.then等。
  • Next tick队列优先于 resolved promise,仅适用于v8提供的原生JS promise。
  • 在Node.js中,setTimeout的最小超时时长为 1ms,尽管设置为0ms,目的是要与Chrome 的计时器上限保持一致。
  • 异步I/O并不是在所有系统支持文件系统的完全异步,为此Node.js引入了线程池。使用非阻塞和异步硬件I/O来完成大部分I/O,对于复杂或阻塞的I/O则使用线程池。