一般来说,Node.js如何处理10,000个并发请求?

时间:2016-01-18 12:56:56

标签: node.js

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

我无法理解它如何比多线程Web服务器更快。我知道多线程Web服务器的资源(内存,CPU)会更加昂贵,但它还不会更快吗?我可能错了;请解释这个单线程如何在大量请求中更快,以及在为10,000个请求提供大量服务时通常会做什么(在高级别)。

而且,那个单线程是否能够很好地扩展?请记住,我刚开始学习Node.js。

7 个答案:

答案 0 :(得分:620)

如果您不得不提出这个问题,那么您可能不熟悉大多数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通常会获胜的地方。

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

因此,如果你用C或go或java编写网络应用程序,那么线程的开销通常不会太糟糕。如果您正在编写一个C Web服务器来为PHP或Ruby提供服务,那么用javascript或Ruby或Python编写速度更快的服务器非常容易。

混合方法

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

某些单线程架构也使用混合方法。您可以启动多个应用程序(例如,四核计算机上的4个node.js服务器),而不是从单个进程启动多个线程。然后使用负载均衡器在进程中分散工作负载。

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

答案 1 :(得分:32)

您似乎在想的是,大多数处理都是在节点事件循环中处理的。节点实际上将I / O工作转移到线程上。 I / O操作通常比CPU操作长几个数量级,那么为什么CPU会等待呢?此外,操作系统已经可以很好地处理I / O任务。实际上,因为Node不会等待它,所以可以实现更高的CPU利用率。

通过类比,将NodeJS视为服务员接受客户订单,而I / O厨师则在厨房准备它们。其他系统有多位厨师,他们接受客户订单,准备餐点,清理餐桌,然后才照顾下一位顾客。

答案 2 :(得分: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限制的工作负载则不太好。

答案 3 :(得分:9)

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

在内部,event loop Node.JS正在处理thread pool,其中每个线程处理一个non-blocking request,并且事件循环在将工作委托给其中一个后继续侦听更多请求thread pool的主题。当其中一个线程完成工作时,它会向event loop发送一个信号,表示它已完成callbackEvent loop然后处理此回调并发回响应。

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

答案 4 :(得分:4)

添加到 slebetman 的答案中,可以更清楚地了解执行代码时发生的情况。

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

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

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

每当一个请求到来时,代码就会按以下顺序在队列中执行回调。

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

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

所有内容都会根据回调的类型排队,并按照上述顺序进行处理。

答案 5 :(得分:1)

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

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

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

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

  • 节点JS Web服务器在内部具有一个组件,称为“事件循环”。 之所以得到这个名字,是因为它使用无限循环来接收 请求并处理它们。

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

  • Even Loop检查是否将任何客户端请求放置在事件队列中。如果 不,然后无限期地等待传入的请求。

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

    1. 启动客户请求的流程
    2. 如果该客户端请求不需要任何阻止IO 操作,然后处理所有内容,准备响应并发送 回到客户。
    3. 如果该客户请求需要执行某些阻止IO操作,例如 然后与数据库,文件系统,外部服务进行交互 将采用不同的方法
  • 从内部线程池检查线程可用性

  • 选择一个线程并将此客户请求分配给该线程。
  • 该线程负责接收,处理该请求, 执行阻止IO操作,准备响应并将其发送回 进入事件循环

    @Rambabu Posa很好地解释了更多解释,请扔这个Link

答案 6 :(得分:0)

这里有一个很好的解释medium article

给定一个 NodeJS 应用程序,因为 Node 是单线程的,如果处理涉及一个需要 8 秒的 Promise.all,这是否意味着在这个请求之后的客户端请求需要等待 8 秒? 不。NodeJS 事件循环是单线程的。 NodeJS 的整个服务器架构不是单线程的。

在进入 Node 服务器架构之前,先来看看典型的多线程请求响应模型,Web 服务器会有多个线程,当并发请求到达 Web 服务器时,Web 服务器从 threadPool 中选择 threadOne,threadOne 处理 requestOne 和响应 clientOne,当第二个请求进来时,Web 服务器从 threadPool 中选取第二个线程并选取 requestTwo 并处理它并响应 clientTwo。 threadOne 负责 requestOne 要求的各种操作,包括执行任何阻塞 IO 操作。

线程需要等待阻塞 IO 操作的事实使其效率低下。使用这种模型,Web 服务器只能处理与线程池中的线程一样多的请求。

NodeJS Web Server 维护一个有限的线程池来为客户端请求提供服务。多个客户端向 NodeJS 服务器发出多个请求。 NodeJS 接收这些请求并将它们放入 EventQueue 中。 NodeJS 服务器有一个称为 EventLoop 的内部组件,它是一个接收请求并处理它们的无限循环。这个 EventLoop 是单线程的。换句话说,Eve​​ntLoop 是 EventQueue 的侦听器。 因此,我们有一个放置请求的事件队列,并且我们有一个事件循环监听事件队列中的这些请求。接下来发生什么? 侦听器(事件循环)处理请求,如果它能够在不需要任何阻塞 IO 操作的情况下处理请求,那么事件循环将自己处理请求并将响应发送回客户端。 如果当前请求使用阻塞 IO 操作,则事件循环查看线程池中是否有可用线程,从线程池中选取一个线程并将特定请求分配给选取的线程。该线程执行阻塞 IO 操作并将响应发送回事件循环,一旦响应到达事件循环,事件循环将响应发送回客户端。

NodeJS 比传统的多线程请求响应模型有什么优势? 在传统的多线程请求/响应模型中,每个客户端都有一个不同的线程,而在 NodeJS 中,更简单的请求都直接由 EventLoop 处理。这是对线程池资源的优化,没有为每个客户端请求创建线程的开销。