通常,Node.js如何处理10,000个并发请求?


393

我知道Node.js使用单线程和事件循环来处理仅一次处理一个请求的请求(这是非阻塞的)。但是,这是如何工作的,可以说有10,000个并发请求。事件循环会处理所有请求吗?那会不会花费太长时间?

我还不了解(但是)它如何比多线程Web服务器更快。我知道多线程Web服务器的资源(内存,CPU)会更昂贵,但是会不会更快?我可能错了;请说明在处理大量请求时此单线程的速度如何,以及在处理诸如10,000之类的大量请求时通常会做什么(高级)。

而且,单线程是否可以很好地扩展此数量?请记住,我才刚刚开始学习Node.js。


4
因为大多数工作(移动数据)不涉及CPU。
OrangeDog

4
还要注意,仅仅因为只有一个线程执行Javascript,并不意味着就没有很多其他线程在工作。
OrangeDog

这个问题要么太宽泛,要么是其他各种问题的重复。
OrangeDog


除单线程外,Node.js还执行称为“非阻塞I / O”的操作。这是所有魔术都完成的地方
Anand N

Answers:


762

如果您必须问这个问题,那么您可能不熟悉大多数Web应用程序/服务的功能。您可能会认为所有软件都可以这样做:

user do an action
       
       v
 application start processing action
   └──> loop ...
          └──> busy processing
 end loop
   └──> send result to user

但是,这不是Web应用程序或任何以数据库为后端的应用程序的工作方式。网络应用程序可以这样做:

user do an action
       
       v
 application start processing action
   └──> make database request
          └──> do nothing until request completes
 request complete
   └──> send result to user

在这种情况下,该软件将大部分运行时间都用0%的CPU时间来等待数据库返回。

多线程网络应用程序:

多线程网络应用程序可以像这样处理上述工作量:

request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request

因此,线程大部分时间都使用0%的CPU等待数据库返回数据。在这样做的同时,他们不得不分配一个线程所需的内存,其中每个线程都包括一个完全独立的程序堆栈。此外,他们还必须启动一个线程,尽管它并不像启动一个完整的进程那样昂贵。便宜的。

单线程事件循环

由于我们大部分时间都使用0%的CPU,为什么不使用CPU时不运行一些代码?这样,每个请求仍将获得与多线程应用程序相同的CPU时间,但是我们不需要启动线程。因此,我们这样做:

request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response

在实践中,两种方法都以大致相同的延迟返回数据,这是因为数据库响应时间决定了处理过程。

这里的主要优点是我们不需要产生新的线程,因此我们不需要执行大量的malloc会减慢我们的速度。

魔术隐形螺纹

看似神秘的事情是上述两种方法如何设法以“并行”方式运行工作负载?答案是数据库是线程化的。因此,我们的单线程应用程序实际上是在利用另一个进程的多线程行为:数据库。

单线程方法失败的地方

如果您需要在返回数据之前进行大量CPU计算,则单线程应用程序会失败很大。现在,我不是说要for循环来处理数据库结果。大部分还是O(n)。我的意思是诸如执行傅立叶变换(例如,mp3编码),光线跟踪(3D渲染)等操作。

单线程应用程序的另一个陷阱是,它将仅利用单个CPU内核。因此,如果您拥有四核服务器(如今并不常见),则您不会使用其他3个核。

多线程方法失败的地方

如果您需要为每个线程分配大量RAM,则多线程应用程序会失败很大。首先,RAM本身的使用量意味着您无法处理与单线程应用程序一样多的请求。更糟糕的是,malloc很慢。分配大量对象(这在现代Web框架中很常见)意味着我们最终可能会比单线程应用程序慢。这是node.js通常获胜的地方。

一个最终使多线程变得更糟的用例是,当您需要在线程中运行另一种脚本语言时。首先,通常需要为该语言分配整个运行时,然后需要分配脚本使用的变量。

因此,如果您使用C或go或java编写网络应用程序,则线程的开销通常不会太糟。如果您要编写C Web服务器来服务PHP或Ruby,那么用javascript,Ruby或Python编写速度更快的服务器非常容易。

混合方式

某些Web服务器使用混合方法。例如,Nginx和Apache2将其网络处理代码实现为事件循环的线程池。每个线程运行一个事件循环,同时处理单线程请求,但请求在多个线程之间进行负载平衡。

一些单线程体系结构也使用混合方法。您可以启动多个应用程序,而不是从单个进程启动多个线程,例如,在四核计算机上启动4个node.js服务器。然后,您可以使用负载平衡器在各个进程之间分配工作负载。

实际上,这两种方法在技术上是彼此相同的镜像。


104
到目前为止,这是我到目前为止所读节点的最佳解释。“单线程应用程序实际上是在利用另一个进程的多线程行为:数据库。”这项工作是
kenobiwan 17-02-26

如果客户端正在节点中发出多个请求该怎么办,例如获得名称并对其进行修改,并说此操作将推送到服务器以使许多客户端非常快速地处理。我该如何处理这种情况?
Remario '17

3
@CaspainCaldion这取决于您所指的是非常快的客户群。照原样,node.js每秒可以处理多达1000个请求,并且速度仅限于网卡的速度。请注意,不是客户端同时连接,而是每秒1000个请求。它可以处理10000个并发客户端,而不会出现问题。真正的瓶颈是网卡。
slebetman

1
@slebetman,有史以来最好的解释。但是,有一件事,如果我有一个机器学习算法来处理一些信息并相应地提供结果,我应该使用多线程方法还是单线程
Ganesh Karewad

5
@GaneshKarewad算法使用CPU,服务(数据库,REST API等)使用I / O。如果AI是用js编写的算法,则应在另一个线程或进程中运行它。如果AI是在另一台计算机上运行的服务(例如Amazon或Google或IBM AI服务),则使用单线程体系结构。
slebetman

46

您似乎想的是,大多数处理是在节点事件循环中处理的。节点实际上将I / O工作分配给线程。I / O操作通常比CPU操作花费几个数量级,那么CPU为什么要等待呢?此外,操作系统已经可以很好地处理I / O任务。实际上,由于Node不等待它,可以提高CPU使用率。

以此类推,可以将NodeJS看作是服务员,在I / O厨师在厨房里准备客户时接受客户的订单。其他系统有多位厨师,他们接一位顾客的订单,准备饭菜,清理桌子,然后再拜访下一位顾客。


5
感谢餐厅的比喻!我发现类比和实际示例非常容易学习。
LaVache

13

我知道Node.js使用单线程和事件循环来处理仅一次处理一个请求的请求(这是非阻塞的)。

我可能会误解您在这里所说的内容,但是“一次一次”听起来似乎您可能没有完全理解基于事件的体系结构。

在“常规”(非事件驱动)应用程序体系结构中,该过程花费大量时间坐在等待发生的事情上。在基于事件的体系结构(例如Node.js)中,过程不仅要等待,还可以继续进行其他工作。

例如:从客户端获得连接,接受连接,读取请求标头(对于http),然后开始对请求执行操作。您可能会阅读请求正文,通常最终将向客户端发送一些数据(这是过程的故意简化,仅用于说明要点)。

在每个阶段中,大部分时间都花在等待另一端的数据到达上-在JS主线程中处理的实际时间通常非常短。

当I / O对象的状态(例如网络连接)发生变化以致需要处理时(例如,在套接字上接收到数据,套接字变得可写等),Node.js JS主线程将通过列表唤醒需要处理的项目。

它找到相关的数据结构,并在该结构上发出一些事件,从而导致运行回调,处理传入数据或将更多数据写入套接字等。一旦所有需要处理的I / O对象都已被处理。在处理完之后,Node.js JS主线程将再次等待,直到被告知有更多数据可用(或者其他操作已完成或超时)。

下次唤醒它,很可能是由于需要处理不同的I / O对象-例如,不同的网络连接。每次运行相关的回调,然后它返回睡眠状态以等待其他事件发生。

重要的一点是,不同请求的处理是交错的,它不会从头到尾处理一个请求,然后再处理另一个请求。

在我看来,这样做的主要优点是请求很慢(例如,您试图通过2G数据连接向移动电话设备发送1MB响应数据,或者您正在执行非常慢的数据库查询)将不会'阻止更快的。

在传统的多线程Web服务器中,通常会为每个正在处理的请求提供一个线程,并且它将仅处理该请求,直到完成为止。如果您有很多慢请求怎么办?您最终将有很多线程挂在处理这些请求的周围,而其他请求(可能是非常简单的请求,可以很快地被处理)被排在它们后面。

除了Node.js之外,还有许多其他基于事件的系统,与常规模型相比,它们往往具有相似的优缺点。

我不会断言基于事件的系统在每种情况下或在每种工作负载下都更快-它们往往适用于受I / O约束的工作负载,而不适用于受CPU约束的工作负载。


12

单线程事件循环模型处理步骤:

  • 客户端将请求发送到Web服务器。

  • 节点JS Web服务器在内部维护一个受限线程池,以为客户端请求提供服务。

  • Node JS Web Server接收这些请求并将其放入队列。它被称为“事件队列”。

  • Node JS Web Server在内部具有一个称为“事件循环”的组件。之所以获得此名称,是因为它使用无限循环来接收请求并对其进行处理。

  • 事件循环仅使用单线程。它是Node JS平台处理模型的主要核心。

  • 事件循环检查是否有任何客户端请求被放置在事件队列中。如果不是,则无限期地等待传入的请求。

  • 如果是,则从事件队列中选择一个客户端请求

    1. 开始客户要求的流程
    2. 如果该客户端请求不需要任何阻塞IO操作,则处理所有内容,准备响应并将其发送回客户端。
    3. 如果该客户请求需要某些阻止IO操作(例如与数据库,文件系统,外部服务进行交互),则它将采用不同的方法
  • 从内部线程池检查线程可用性
  • 拾取一个线程并将此客户请求分配给该线程。
  • 该线程负责处理该请求,处理该请求,执行阻塞IO操作,准备响应并将其发送回事件循环

    @Rambabu Posa很好地解释了更多解释,请抛出此链接


该博客文章中给出的图表似乎是错误的,他们在该文章中提到的内容并不完全正确。
rranj

11

slebetman的答案:当您说Node.JS可以处理10,000个并发请求时,它们本质上是非阻塞请求,即这些请求主要与数据库查询有关。

在内部,event loopNode.JS受理的thread pool,其中每个线程处理一个non-blocking request和事件循环继续委托工作的线程之一后,听取更多的要求thread pool。当其中一个线程完成工作时,它向发出信号,event loop即aka已完成callbackEvent loop然后处理此回调并将响应发送回去。

当您刚接触NodeJS时,请阅读更多有关nextTick了解事件循环在内部如何工作的信息。阅读http://javascriptissexy.com上的博客,当我开始使用JavaScript / NodeJS时,它们对我真的很有帮助。


2

slebetman的答案中添加了更多信息,以使执行代码时更清楚。

默认情况下,nodeJs中的内部线程池只有4个线程。并且它并不像整个请求都被附加到线程池中的新线程上一样,整个请求的执行就像任何普通请求一样(没有任何阻塞任务),就像每当一个请求长时间运行或像db这样繁重的操作一样调用,文件操作或http请求,任务将排队到libuv提供的内部线程池中。而且,由于nodeJs默认在内部线程池中提供4个线程,因此每5个或下一个并发请求将等待直到一个线程空闲,并且一旦这些操作结束,回调便被推入回调队列。并由事件循环接收并发回响应。

现在出现了另一个信息,它不再是单个回调队列,而是有很多队列。

  1. NextTick队列
  2. 微任务队列
  3. 计时器队列
  4. IO回调队列(请求,文件操作,数据库操作)
  5. IO轮询队列
  6. 检查阶段队列或SetImmediate
  7. 关闭处理程序队列

每当请求到来时,代码都将按此回调顺序排队执行。

它不像有阻止请求时,它被附加到新线程上。默认情况下只有4个线程。因此,那里还有另一个队列正在发生。

每当在代码中发生诸如文件读取之类的阻塞过程时,然后调用一个利用线程池中的线程的函数,然后在完成该操作后,将回调传递给相应的队列,然后按顺序执行。

一切都会根据回调的类型排队,并按上述顺序进行处理。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.