NodeJS体系结构–单线程事件循环
今天,我们将研究Node JS体系结构和单线程事件循环模型。
在之前的文章中,我们讨论了有关Node JS基础知识,Node JS组件和Node JS安装的信息。
NodeJS架构
在开始一些Node JS编程示例之前,了解Node JS架构很重要。
在本文中,我们将讨论" Node JS的工作原理,其遵循的处理模型类型,Node JS如何处理单线程模型的并发请求"等内容。
NodeJS单线程事件循环模型
正如我们已经讨论的那样,Node JS应用程序使用"单线程事件循环模型"体系结构来处理多个并发客户端。
有许多Web应用程序技术,例如JSP,Spring MVC,ASP.NET,HTML,Ajax,jQuery等。
但是所有这些技术都遵循"多线程请求-响应"架构来处理多个并发客户端。
我们已经熟悉"多线程请求-响应"架构,因为大多数Web应用程序框架都使用了该架构。
但是为什么Node JS Platform选择了不同的体系结构来开发Web应用程序。
多线程和单线程事件循环体系结构之间的主要区别是什么?
任何Web开发人员都可以学习Node JS并非常轻松地开发应用程序。
但是,如果不了解Node JS Internals,我们就无法很好地设计和开发Node JS应用程序。
因此,在开始开发Node JS应用程序之前,首先我们将学习Node JS Platform内部。
Node JS平台
Node JS平台使用"单线程事件循环"架构来处理多个并发客户端。
然后,它如何真正在不使用多个线程的情况下处理并发客户端请求。
什么是事件循环模型?我们将一一讨论这些概念。
在讨论"单线程事件循环"体系结构之前,首先我们将介绍著名的"多线程请求-响应"体系结构。
传统的Web应用程序处理模型
在没有Node JS的情况下开发的任何Web应用程序通常都遵循"多线程请求-响应"模型。
简单地说,我们可以将此模型称为请求/响应模型。
客户端将请求发送到服务器,然后服务器根据客户端的请求进行一些处理,准备响应并将其发送回客户端。
该模型使用HTTP协议。
由于HTTP是无状态协议,因此此请求/响应模型也是无状态模型。
因此,我们可以将其称为"请求/响应无状态模型"。
但是,此模型使用多个线程来处理并发客户端请求。
在讨论该模型的内部原理之前,请首先浏览下图。
请求/响应模型处理步骤:
客户端将请求发送到Web服务器。
Web Server在内部维护一个有限线程池,以向客户端请求提供服务。
Web服务器处于无限循环中,正在等待客户端传入请求
Web服务器收到这些请求。
Web服务器接收一个客户端请求从线程池中拾取一个线程
将此线程分配给客户请求
该线程将负责读取客户端请求,处理客户端请求,执行任何阻止IO操作(如果需要)并准备响应
该线程将准备好的响应发送回Web服务器
Web服务器依次将此响应发送到相应的客户端。
服务器在无限循环中等待,并为所有n个客户端执行上述所有子步骤。
这意味着该模型为每个客户端请求创建一个线程。
如果更多的客户端请求需要阻止IO操作,则几乎所有线程都在忙于准备其响应。
然后其余的客户请求应该等待更长的时间。
图表说明:
在此," n"个客户端向Web服务器发送请求的数量。
让我们假设他们正在同时访问我们的Web应用程序。让我们假设,我们的客户是Client-1,Client-2…和Client-n。
Web Server在内部维护受限线程池。
让我们假设线程池中的线程数为" m"。Web服务器一个接一个地接收这些请求.Web服务器拾取Client-1请求-1,从线程池中拾取一个线程T-1并将此请求分配给线程T-1线程T-1读取Client-1请求-1并处理它
客户端1请求1不需要任何阻塞IO操作
线程T-1执行必要的步骤并准备Response-1,并将其发送回服务器
Web服务器依次将此响应1发送到客户端1
Web服务器拾取另一个Client-2 Request-2,从线程池中拾取一个线程T-2,并将此请求分配给线程T-2线程T-2读取Client-1 Request-2并对其进行处理
客户端1请求2不需要任何阻塞IO操作
线程T-2执行必要的步骤并准备Response-2,并将其发送回服务器
Web服务器依次将此响应2发送到客户端2
Web服务器拾取另一个Client-n Request-n,从线程池中拾取一个线程T-n,并将此请求分配给线程T-nThread T-n读取Client-n Request-n并对其进行处理
客户端-请求-需要大量的阻塞IO和计算操作
线程T-n需要更多时间与外部系统进行交互,执行必要的步骤并准备Response-n并将其发送回服务器
Web服务器依次将此响应-n发送到客户端-n
如果" n"大于" m"(大多数情况下为true),则服务器将线程分配给客户端请求,直至可用线程。
在使用完所有m个线程之后,其余的客户请求应在队列中等待,直到一些繁忙的线程完成其请求处理作业并可以自由接听下一个请求。
If those threads are busy with Blocking IO Tasks (For example, interacting with Database, file system, JMS Queue, external services etc.) for longer time, then remaining clients should wait longer time.- 一旦线程在线程池中可用并且可用于下一个任务,服务器就会拾取这些线程并将其分配给其余的客户端请求。
- 每个线程利用许多资源,例如内存等。
因此,在使这些线程从繁忙状态进入等待状态之前,它们应释放所有获取的资源。
请求/响应无状态模型的缺点:
处理越来越多的并发客户的请求有点困难。
当并发客户端请求增加时,它应该使用越来越多的线程,最后它们会消耗更多的内存。
有时,客户的请求应等待可用线程处理其请求。
浪费时间处理阻塞IO任务。
NodeJS体系结构–单线程事件循环
Node JS平台未遵循请求/响应多线程无状态模型。
它遵循单线程事件循环模型。
Node JS Processing模型主要基于Java事件基于Java回调机制的模型。
您应该对Javascript事件和回调机制的工作原理有一些了解。
如果您不知道,请先阅读这些文章或者教程,并有所了解,然后再继续进行本文的下一步。
由于Node JS遵循此体系结构,因此可以非常轻松地处理越来越多的并发客户端请求。
在讨论该模型的内部原理之前,请首先浏览下图。
我试图设计该图来解释Node JS Internals的每个方面。
Node JS处理模型的主要核心是"事件循环"。
如果我们理解这一点,那么很容易理解Node JS Internals。
单线程事件循环模型处理步骤:
客户端将请求发送到Web服务器。
NodeJS Web服务器在内部维护一个受限线程池,以为客户端请求提供服务。
Node JS Web Server接收这些请求并将其放入队列。
它被称为"事件队列"。Node JS Web Server在内部具有一个称为"事件循环"的组件。
之所以获得这个名称,是因为它使用无限循环来接收请求并处理它们。
(请参阅下面的一些Java Pseudo代码以了解这一点)。事件循环仅使用单线程。
它是Node JS平台处理模型的主要核心。甚至循环检查是否将任何客户端请求放置在事件队列中。
如果否,则无限期等待传入的请求。如果是,则从事件队列启动中拾取一个客户端请求,以处理该客户端请求
如果该客户端请求不需要任何阻塞IO操作,则处理所有内容,准备响应并将其发送回客户端。
如果该客户端请求需要某些阻止IO操作(例如与数据库,文件系统,外部服务进行交互),则它将采用不同的方法从内部线程池中检查线程可用性
拾取一个线程并将此客户请求分配给该线程。
该线程负责处理该请求,处理该请求,执行阻塞IO操作,准备响应并将其发送回事件循环
事件循环依次将响应发送到相应的客户端。
图表说明:
在此," n"个客户端向Web服务器发送请求的数量。
让我们假设他们正在同时访问我们的Web应用程序。让我们假设,我们的客户是Client-1,Client-2…和Client-n。
Web Server在内部维护受限线程池。
让我们假设线程池中的线程数为" m"。NodeJS Web服务器接收Client-1,Client-2…和Client-n请求,并将它们放置在事件队列中。
NodeJS偶数循环逐个拾取这些请求。
偶数循环拾取Client-1 Request-1检查Client-1 Request-1是否确实需要任何阻塞IO操作或者花费更多时间进行复杂的计算任务。由于此请求是简单的计算和无阻塞IO任务,因此它不需要单独的线程来处理它。
事件循环处理Client-1 Request-1操作(此处的操作表示Java Script的功能)中提供的所有步骤,并准备Response-1
事件循环将响应1发送到客户端1
甚至Loop拾取Client-2 Request-2都检查Client-2 Request-2是否需要任何阻塞IO操作还是需要更多时间来执行复杂的计算任务。
由于此请求是简单的计算和无阻塞IO任务,因此它不需要单独的线程来处理它。
事件循环处理该Client-2 Request-2操作中提供的所有步骤,并准备Response-2
事件循环将响应2发送到客户端2
偶数循环拾取Client-n Request-n检查Client-n Request-n是否确实需要任何阻塞IO操作还是需要更多时间来执行复杂的计算任务。
由于此请求是非常复杂的计算或者阻塞IO任务,因此Even Loop不会处理此请求。
事件循环从内部线程池中拾取线程T-1,并将此Client-n Request-n分配给线程T-1
线程T-1读取并处理Request-n,执行必要的Blocking IO或者Computation任务,最后准备Response-n
线程T-1将此Response-n发送到事件循环
事件循环依次将此响应-n发送给客户端-n
此处,客户端请求是对一个或者多个Java脚本函数的调用。
Java脚本功能可以调用其他功能,也可以利用其回调功能的性质。
因此,每个客户请求如下所示:
例如:
function1(function2,callback1); function2(function3,callback2); function3(input-params);
注意:
如果您不了解这些函数的执行方式,那么我觉得您对Java脚本函数和回调机制不熟悉。
我们应该对Java脚本功能和回调机制有所了解。
在开始我们的Node JS应用程序开发之前,请阅读一些在线教程。
NodeJS体系结构–单线程事件循环的优势
处理越来越多的并发客户的请求非常容易。
即使我们的Node JS应用程序接收到越来越多的并发客户端请求,由于事件循环,也不需要创建越来越多的线程。
Node JS应用程序使用更少的线程,因此只能使用更少的资源或者内存
事件循环伪代码
由于我是Java开发人员,因此我将尝试用Java术语解释"事件循环的工作原理"。
它不是纯Java代码,我想每个人都可以理解。
如果您在理解此问题时遇到任何问题,请给我留言。
public class EventLoop { while(true){ if(Event Queue receives a JavaScript Function Call){ ClientRequest request = EventQueue.getClientRequest(); If(request requires BlokingIO or takes more computation time) Assign request to Thread T1 Else Process and Prepare response } } }