NodeJS架构 – 单线程事件循环模型

4次阅读

共计 3636 个字符,预计需要花费 10 分钟才能阅读完成。

这篇译章探究了 NodeJS 的架构和单线程事件循环模型。我们将在本文中讨论“NodeJS 如何在底层工作,它遵循什么类型的处理模型,NodeJS 如何使用单线程模型处理并发请求”等内容。
NodeJS 单线程事件循环模型
正如我们刚才说的,NodeJS 使用的是“单线程事件循环模型”的架构去处理多个并发的客户端请求的。
有许多 Web 应用程序技术,如 JSP,Spring MVC,ASP.NET 等。但所有这些技术都遵循“多线程请求 – 响应”架构来处理多个并发客户端。
我们已经熟悉“多线程请求 – 响应”架构,因为它被大多数 Web 应用程序框架使用。但是为什么 NodeJS 选择了不同的架构来开发 Web 应用程序。多线程和单线程事件循环体系结构之间的主要区别是什么?
NodeJS
NodeJS 使用“单线程事件循环模型”架构来处理多个并发客户端。然而它是如何真正处理并发客户端请求且不使用多个线程。什么是事件循环模型?我们将逐一讨论这些概念。
在讨论“单线程事件循环”架构之前,首先我们将介绍著名的“多线程请求 – 响应”架构。
传统的 Web 应用处理模型
任何非 NodeJS 开发的 Web 应用程序通常都遵循“多线程请求 – 响应”模型。我们可以将此模型称为请求 / 响应模型。
客户端向服务器发送请求,然后服务器根据客户端请求进行一些处理,准备响应并将其发送回客户端。
该模型使用 HTTP 协议。由于 HTTP 是无状态协议,因此该请求 / 响应模型也是无状态模型。所以我们可以将其称为请求 / 响应无状态模型。
但是,此模型使用多线程来处理并发客户端请求。在讨论这个模型内部之前,首先要看下面的内容。
请求 / 响应模型处理的步骤:

客户端发送一个请求到 Web 服务器
Web 服务器内部维护一个有限的线程池,以便在客户端请求提供服务
Web 服务器处于无限循环中并等待客户端传入请求

Web 服务器处理请求步骤:

接收到一个客户端请求
从线程池中选择一个线程
将此线程分配给客户端请求
此线程读取客户端请求,处理客户端请求,执行阻塞的 IO 操作 (如果需要) 和准备响应
此线程将准备好的请求发送回 Web 服务器
Web 服务器又将此响应发送到相应的服务器

服务器为所有客户端执行以上步骤,为每一个客户端请求创建一个线程。

图表说明:

Client-1, Client-2, …, Client- n 是同时发送请求到 Web 服务器的客户端应用
Web 服务器内部维护着一个有限的线程池,线程池中线程数量为 m 个

Web 服务器逐个接收这些请求:

Web 服务器拾取 Client- 1 的请求 Request-1,从线程池中拾取一个线程 T - 1 并将此请求分配给线程 T -1

线程 T - 1 读取 Client- 1 的请求 Request-1, 并处理该请求
该请求无阻塞 IO 处理
处理完必要的步骤后准备将 Response- 1 发送回客户端
Web 服务器又将此 Response- 1 发送到 Client-1

Web 服务器拾取 Client- 2 的请求 Request-2,从线程池中拾取一个线程 T - 2 并将此请求分配给线程 T -2

线程 T - 2 读取 Client- 2 的请求 Request-2, 并处理该请求
该请求无阻塞 IO 处理
处理完必要的步骤后准备将 Response- 2 发送回客户端
Web 服务器又将此 Response- 2 发送到 Client-2

Web 服务器拾取 Client- n 的请求 Request-n,从线程池中拾取一个线程 T - n 并将此请求分配给线程 T -n

线程 T - n 读取 Client- n 的请求 Request-n, 并处理该请求
Request- n 需要大量的阻塞 IO 和计算操作
线程 T - n 需要更多时间与外部系统 (SQL, File System) 交互,执行必要步骤并准备 Response- n 并将其发送回服务器
Web 服务器又将此 Response- n 发送到 Client-n

如果 ’n’ 大于 ’m’(大多数时候, 它是真的),则在使用完所有的 m 个线程之后,剩余的客户端请求会在队列中等待。
如果这些线程中有大量的阻塞 IO 操作(例如: 和数据库、文件系统、外部服务等交互),那么剩余的客户端也会等待更长的时间。

一旦线程池中的线程空闲且可用于下一个任务,服务器就会拾取这些线程并将它们分配给剩余的客户端请求。
每个线程都会使用到许多资源,如内存等。因此,在将这些线程从忙状态转到等待状态之前,它们应该释放所有获取的资源。

请求 / 响应无状态模型的缺点:

在处理越来越多的并发客户端请求时会变得棘手
当客户端请求增加时,线程也会越来越多,最后它们会占用更多内存。
客户端可能需要等待服务器释放可用的线程去处理其请求
处理阻塞式的 IO 任务时浪费时间

NodeJS 的架构 – 单线程事件循环
NodeJS 不遵循请求 / 响应多线程无状态模型。它采用单线程与事件循环模型。NodeJS 的处理模型主要基于 Javascript 基于事件的模型和 Javascript 回调机制。
因为 NodeJS 遵循的架构,它可以非常轻松地处理越来越多的并发客户端请求。在讨论这个模型内部之前,首先要看下面的图表。
我试图设计这个图来解释 NodeJS 内部的每一点。
NodeJS 的处理模型主要核心是“事件循环(Event Loop)”。如果我们理解这一点,那么很容易理解 NodeJS 的内部架构的。
单线程事件循环模型的处理步骤

客户端发送请求到 Web 服务器
NodeJS 的 Web 服务器在内部维护一个有限的线程池,以便为客户端请求提供服务
NodeJS 的 Web 服务器接收这些请求并将它们放入队列中。它被称为“事件队列”
NodeJS 的 Web 服务器内部有一个组件,称为“事件循环”,它使用无限循环来接收请求并处理它们。
事件循环只使用到了一个线程,它是 NodeJS 的处理模型的核心
事件循环回去检查是否有客户端的请求被放置在事件队列中。如果没有,会一直等待事件队列中存在请求。

如果有,则会从事件队列中拾取一个客户端请求:

开始处理客户端请求
如果该客户端请求不需要任何阻塞 IO 操作,则处理所有内容,准备响应并将其发送回客户端

如果该客户端请求需要一些阻塞 IO 操作,例如与数据库,文件系统,外部服务交互,那么它将遵循不同的方法:

从内部线程池检查线程可用性
获取一个线程并将此客户端请求分配给该线程
该线程负责接收该请求,处理该请求,执行阻塞 IO 操作,准备响应并将其发送回事件循环
事件循环依次将响应发送到相应的客户端

图表说明:

Client-1, Client-2, …, Client- n 是同时发送请求到 Web 服务器的客户端应用
Web 服务器内部维护着一个有限的线程池,线程池中线程数量为 m 个
NodeJS 的 Web 服务器接收到 Client-1, Client-2, …, Client- n 的请求后,将请求放入到事件队列中

NodeJS 的事件循环从队列中开始拾取这些请求:

事件循环拾取 Client- 1 的请求 Request-1

检查 Client-1 Request- 1 是否确实需要任何阻塞 IO 操作,或者需要更多时间来执行复杂的计算任务
由于此请求是简单计算和非阻塞 IO 任务,因此不需要单独的线程来处理它
事件循环处理该请求所需要的操作,准备其响应 Response-1
事件循环发送 Response- 1 到 Client-1

事件循环拾取 Client- 2 的请求 Request-2

检查 Client-2 Request- 2 是否需要任何阻塞 IO 操作或花费更多时间来执行复杂的计算任务
由于此请求是简单计算和非阻塞 IO 任务,因此不需要单独的线程来处理它
事件循环处理该请求所需要的操作,准备其响应 Response-2
事件循环发送 Response- 2 到 Client-2

事件循环拾取 Client- n 的请求 Request-n

检查 Client-n Request- n 是否需要任何阻塞 IO 操作或花费更多时间来执行复杂的计算任务
由于此请求有非常复杂的计算或阻塞 IO 任务,因此事件循环不会处理此请求
事件循环从内部线程池中获取线程 T -1,并将此 Client-n Request- n 分配给线程 T -1
线程 T - 1 读取并处理 Request-n,执行必要的阻塞 IO 或计算任务,最后准备响应 Response-n
线程 T - 1 将此 Response- n 发送到事件循环

事件循环依次将此 Response- n 发送到 Client-n
此处客户端请求是对一个或多个 JavaScript 函数的调用,因为 JavaScript 函数可以调用其他函数或可以利用其回调函数性质。
此所以每个客户端的请求处理都看起来向这样:

例如:
function1(function2,callback1);
function2(function3,callback2);
function3(input-params);
NodeJS 的单线程事件循环的优势

处理越来越多的并发客户端请求非常容易
因为事件循环的存在,即使我们的 NodeJS 应用接收到了越来越多的并发请求,我们也不需要去新建很多的线程
NodeJS 使用到了较少的线程,所以资源和内存的使用较少

原文地址: NodeJS Architecture – Single Threaded Event Loop

正文完
 0