您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
Node JS体系架构 - 单线程事件循环
 
  3015  次浏览      17
  2018-5-11 
 
编辑推荐:
本文来自网络 ,今天让我们来了解Node JS的体系架构和单线程事件循环模型。

Node JS体系架构

在开始学习Node JS编程示例前,了解Node JS的体系架构是十分重要的。我们将要讨论,Node JS的底层工作原理是什么,它遵循怎样的处理模型,以及它是如何使用单线程模型处理来自客户端的并发请求。

Node JS 单线程事件循环模型

之前提到,Node JS应用使用“单线程事件循环模型”(Single Threaded Event Loop Model)处理来自客户端的多并发请求。

现在有很多的Web应用开发技术,例如JSP,Spring MVC,ASP.NET,HTML,Ajax,jQuery等。但是所有的这些技术都是遵循“多线程请求/响应”(Multi-Threaded Request-Response)的结构去处理来自客户端的多并发请求。

由于“多线程请求/响应“结构已经在大量的web应用框架中使用,因此我们应该已经对其有所了解。但是为什么Node JS却选择了与这些框架不同的体系结构呢。多线程结构和单线程事件循环结构的主要区别是什么呢。

任何一个开发者都可以轻松的学会Node JS并使用它开发程序。但是如果不了解Node JS的内部机制,则将会无法更好地设计和开发Node JS应用。因此在开发前,有必要先了解Node JS平台的内部机理。

Node JS 平台

Node JS平台使用“单线程事件循环模型”处理来自客户端的多并发请求。但是它是如何在不使用多线程的请胯下处理多并发请求呢?事件循环模型又是什么呢。我们将逐一进行讲解。

在讨论“单线程事件循环模型”之前,首先来回顾下“多线程请求-响应”结构。

传统的Web应用处理模型

当今大多数未使用Node JS进行开发的Web应用,基本上都是遵循“多线程请求-响应”结构,简称为”请求/响应模型“(Request/Response Model)。

客户端将请求发送给服务端,服务端根据请求进行处理,准备响应(数据),并将其返回给客户端。

这个模型使用HTTP协议。由于HTTP是无状态协议,因此”请求/响应模型“也是无状态的模型。所以我们又可以称其为”无状态请求/响应模型“(Request/Response Stateless Model)。

简而言之,这个模型使用的是多线程来处理客户端的多并发请求。在讨论其内部机理前,我们先通过下面的图进行概览。

”请求/响应模型“的处理步骤

客户端向服务端发送请求

Web服务端在内部维护一个有个数限制的线程池,为客户端的请求提供服务

Web服务端循环监听来自客户端的请求

Web服务端收到请求

Web服务端选择一个客户端请求

从线程池(Thread pool)选择一个线程(Thread)

将该线程分配给刚选择的请求

该线程将会负责读取,处理客户端请求,执行任何I/O阻塞的操作(如果需要的话),以及准备响应的内容

该线程将准备就绪的响应发送给Web服务端

Web服务端依次将响应回执给客户端

服务器采用无线循环监听客户端的请求,针对所有的客户端请求执行上述的所有步骤。这意味着对于每一个客户端请求,该模型(请求/响应模型)都要为其创建一个线程。

如果更多的客户端请求需要I/O阻塞操作的化,那么所有的线程将在准备响应阶段处于繁忙状态。这就意味着后续的客户端请求需要等待更长的时间才能得到响应。

图示描述:

这里有“n“个客户端向服务端发送请求,假设它们是并发进入Web应用程序

假设客户端分别为Client-1,Client-2……,Client-n

Web服务端维护一个有个限的线程池。假设在线程池中的线程个数是”m”

Web服务端依次接收客户端发送的请求

Web服务端选择客户端Client-1的Request-1请求,并且从线程池中选择线程T-1作为处理该请求的线程。

线程T-1读取客户端Client-1的Request-1请求,进行处理

Request-1请求不需要I/O阻塞的操作

线程T-1执行必须的操作,准备响应Response-1,并将其发送给服务端

服务端依照响应次序将Response-1回执给Client-1

Web服务端选择客户端Client-2的Request-2请求,并且从线程池中选择线程T-2作为处理该请求的线程。

线程T-2读取客户端Client-2的Request-2请求,进行处理

Request-2请求不需要I/O阻塞的操作

线程T-2执行必须的操作,准备响应Response-2,并将其发送给服务端

服务端依照响应次序将Response-2回执给Client-2

Web服务端选择客户端Client-n的Request-n请求,并且从线程池中选择线程T-n作为处理该请求的线程。

线程T-n读取客户端Client-2的Request-n请求,进行处理

Request-n请求需要较重的I/O阻塞操作和运算操作

线程T-n会花费更多的时间,和外部系统进行交互,并执行必须的操作,准备响应Response-n,并将其发送给服务端

服务端依照响应次序将Response-n回执给Client-n

如果n>m(大多情况都是这样),也就是说需要分配给请求的线程数要大于可用的线程数。当所有的线程被使用时,那么剩余的客户端请求就在队列中等待,直到一些处于繁忙状态的线程完成对负责线程的处理任务,改变状态为空闲。

如果线程都长时间处于I/O繁忙的状态(例如,与数据库(Database),文件系统(file system),JMS队列(JMS Queue),外部服务( external services)等)。则剩余未处理的请求则将等待很长时间。

当线程池的一些线程已经为执行下个任务准备就绪时,服务端将这些线程分配给剩余的客户端请求

每个线程都要使用许多的资源,例如内存等。所以在线程从繁忙状态变为空闲状态时,需要释放所有占用的资源

”请求/响应模型“的缺点

在处理大量增加的客户端请求时效率较差

当并发的客户端请求增加时,就需要更多的线程,会导致大量的内存被占用

有时候,客户端请求需要等待可用的线程来处理它们的请求

浪费大量的时间处理I/O阻塞的任务

Node JS体系结构——单线程事件循环

Node JS平台不遵循”多线程无状态的请求/响应模型“,而是采用单线程事件循环模型。它的处理模型主要是基于JavaScript基本事件模型和回调函数机制的结合。

你应该对JavaScript事件和回调函数机制如何运作有了较好的了解。如果没有,请在阅读下面的内容前优先了解基本内容,以帮助理解。

由于Node JS遵循这个体系(单线程事件循环)。因此它可以轻松地处理越来越多的客户端并发请求。在讨论其内部机理前,我们先通过下面的图进行概览。

其处理的主要核心是“事件循环”(Event Loop)。如果了解了这个,那么明白它内部机制就会相对轻松一些。

”单线程事件循环模型“的处理步骤

客户端向服务端发送请求

Node JS服务端在内部维护一个有个数限制的线程池,为客户端的请求提供服务

Node JS服务端循环监听来自客户端的请求,并将它们放置到一个队列中,这个队列被称为“事件队列”(Event Queue)。

Node JS内部存在一个叫做“事件循环”(Event Loop)的组件。它通过无限循环来接收请求并进行处理。(可以通过下面的Java伪代码加深了解)

事件循环组件只使用单线程。它是Node JS平台处理模型的核心

事件循环组件会检查所有在事件队列中的客户端请求。如果没有,则继续等待请求。

如果存在请求,则从事件队列中提取一个请求

开始处理该请求

如果该请求不需要I/O阻塞的操作,则处理必须的工作,准备响应的内容,并将准备就绪的响应发送给客户端

如果该请求需要I/O阻塞的操作,如与数据库(Database),文件系统(file system),外部服务( external services)等,则使用不同的处理流程

检测内部线程池是否存在可用线程

如果有,则从内部线程池提取一个线程,并将其分配处理客户端的请求

线程负责读取,读取,处理客户端请求,执行任何I/O阻塞的操作,准备响应的内容,并将准备就绪的响应发送给事件循环组件

该线程将会负责读取,处理客户端请求,执行任何I/O阻塞的操作(如果需要的话),以及准备响应的内容

该线程将准备就绪的响应发送给Web服务端

事件循环组件依次将响应回执给客户端

图示描述:

这里有“n“个客户端向服务端发送请求,假设它们是并发进入Web应用程序

假设客户端分别为Client-1,Client-2……,Client-n

Web服务端维护一个有个限的线程池。假设在线程池中的线程个数是”m”

Node JS服务端接收请求Client-1,Client-2……,Client-n,并将它们放入事件队列

Node JS的事件循环组件依次提取这些请求

事件循环组件选择客户端Client-1的Request-1请求

检测该请求是需要I/O阻塞操作还是复杂的计算任务

如果只是简单的计算而无I/O阻塞任务,则不需要额外的线程去处理

事件循环组件执行请求中提供的所有运算(这里的运算是指JavaScript的方法)并且准备响应Response-1

事件循环组件将Response-1回执给Client-1

事件循环组件选择客户端Client-2的Request-2请求

检测该请求是需要I/O阻塞操作还是复杂的计算任务

如果只是简单的计算而无I/O阻塞任务,则不需要额外的线程去处理

事件循环组件执行请求中提供的所有运算(这里的运算是指JavaScript的方法)并且准备响应Response-2

事件循环组件将Response-2回执给Client-2

事件循环组件选择客户端Client-n的Request-n请求

检测该请求是需要I/O阻塞操作还是复杂的计算任务

由于该请求是复杂的运算或I/O阻塞任务,因此事件循环组件不去处理

事件循环组件从内部线程池提取线程T-1,并分配其去处理请求Request-n的任务

线程T-1处理请求Request-n,执行必要的I/O阻塞和计算任务,并准备响应Response-n

线程T-1将响应Response-n回执给事件循环组件

事件循环组件按照次序将Response-n回执给Client-n

这里的客户端请求是一个或多个JavaScript函数。该函数可以调用其他函数或是使用回调函数(Callback function)

每个客户端请求格式如下所示:

function(other-functioncall, callback-function)

例如:

function1(function2,callback1);
function2(function3,callback2);
function3(input-params);

注意:

如果你不理解这些函数是如何执行的,那么可能你对JavaScript的函数和回调机制不够熟悉

我们应该对JavaScript函数和回调机制有所了解。因此在开始开发Node JS应用前请先通过网上的相关教程进行学习

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
}
}
}

这就是关于Node JS体系结构和单线程事件循环的全部内容。

   
3015 次浏览       17
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程