ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

而且,单线程会在这么大的数量下很好地扩展吗?请记住,我刚刚开始学习 Node.js。

因为大部分工作(移动数据)不涉及 CPU。
另请注意,仅仅因为只有一个线程执行 Javascript,并不意味着没有很多其他线程在工作。
这个问题要么太宽泛,要么与其他各种问题重复。
除了单线程之外,Node.js 还做了一些称为“非阻塞 I/O”的事情。这里是所有魔法完成的地方

s
slebetman

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

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

然而,这不是 Web 应用程序或任何以数据库为后端的应用程序的工作方式。 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 网络服务器来服务 PHP 或 Ruby,那么用 javascript、Ruby 或 Python 编写一个更快的服务器是非常容易的。

混合方法

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

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

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


这是迄今为止我读过的对节点的最佳解释。那个“单线程应用程序实际上是利用另一个进程的多线程行为:数据库。”完成了工作
@CaspainCaldion这取决于您所说的非常快和很多客户的意思。照原样,node.js 每秒可以处理超过 1000 个请求,并且速度仅限于网卡的速度。请注意,每秒 1000 个请求不是同时连接的客户端。它可以毫无问题地同时处理 10000 个客户端。真正的瓶颈是网卡。
@slebetman,有史以来最好的解释。但是有一件事,如果我有一个机器学习算法可以处理一些信息并相应地提供结果,我应该使用多线程方法还是单线程
@GaneshKarewad 算法使用 CPU,服务(数据库、REST API 等)使用 I/O。如果 AI 是用 js 编写的算法,那么您应该在另一个线程或进程中运行它。如果 AI 是在另一台计算机上运行的服务(如 Amazon、Google 或 IBM AI 服务),则使用单线程架构。
@VikasDubey 请注意,节点服务器根本不处理视频流。 99% 的处理发生在浏览器/媒体播放器中。在这种情况下,node.js 只不过是一个文件服务器,这意味着它利用了操作系统的并行磁盘/网络 I/O 功能。对于网络 I/O,大多数操作系统都具有同样的能力,但对于磁盘 I/O,如果与 Btrfs 或 ext4 等快速文件系统一起使用,Linux 往往胜过其他所有操作系统(当然,RAID 使几乎一切都变得更快)
c
chriskelly

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

打个比方,把 NodeJS 想象成一个服务员,当 I/O 的厨师在厨房里准备他们的订单时,服务员会接受客户的订单。其他系统有多个厨师,他们接受顾客的订单、准备餐点、收拾桌子,然后才照顾下一位顾客。


感谢餐厅的比喻!我发现类比和现实世界的例子更容易学习。
非常好衔接。好比喻!
node 的 js 部分是单线程的,而底层的 c++ 部分使用的是线程池。 stackoverflow.com/a/70161215/4034825
在餐厅示例中:如果我们有 10000 名顾客和一名服务员怎么办?用一个服务员(一个线程)处理这些客户不是很慢吗?
P
Piyush Balapure

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

客户端向 Web 服务器发送请求。

Node JS Web 服务器内部维护了一个有限线程池来为客户端请求提供服务。

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

Node JS Web Server 内部有一个组件,称为“事件循环”。它之所以得这个名字是因为它使用无限循环来接收请求并处理它们。

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

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

如果是,则从事件队列中提取一个客户端请求开始处理该客户端请求如果该客户端请求不需要任何阻塞 IO 操作,则处理所有内容,准备响应并将其发送回客户端。如果该客户端请求需要一些阻塞 IO 操作,例如与数据库、文件系统、外部服务交互,那么它将采用不同的方法

启动客户端请求的过程

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

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

从内部线程池检查线程可用性

拾取一个线程并将此客户端请求分配给该线程。

该线程负责接受该请求,处理它,执行阻塞 IO 操作,准备响应并将其发送回事件循环,@Rambabu Posa 很好地解释了更多解释去扔这个链接


该博客文章中给出的图表似乎是错误的,他们在那篇文章中提到的内容并不完全正确。
AFAIK,节点中没有阻塞 I/O(除非您使用同步 API),线程池仅用于暂时处理 I/O 响应并将它们传递给主线程。但是,在等待 I/O 请求时 [there's no thread]( blog.stephencleary.com/2013/11/there-is-no-thread.html) ,否则线程池会很快被堵塞。
s
sheltond

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

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

在“常规”(非事件驱动)应用程序架构中,该过程花费大量时间坐在那里等待某事发生。在 Node.js 等基于事件的架构中,进程不只是等待,它可以继续其他工作。

例如:您从客户端获得连接,接受它,读取请求标头(在 http 的情况下),然后开始对请求采取行动。您可能会阅读请求正文,通常最终会将一些数据发送回客户端(这是对过程的故意简化,只是为了说明这一点)。

在每个阶段,大部分时间都花在等待一些数据从另一端到达——在主 JS 线程中处理的实际时间通常相当少。

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

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

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

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

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

在传统的多线程 Web 服务器中,您通常会为每个正在处理的请求设置一个线程,并且它只会处理该请求,直到完成。如果你有很多缓慢的请求会发生什么?您最终会遇到很多线程在处理这些请求,而其他请求(可能是可以非常快速地处理的非常简单的请求)在它们后面排队。

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

我不会声称基于事件的系统在每种情况下或每种工作负载下都更快 - 它们往往适用于 I/O 密集型工作负载,而不适用于 CPU 密集型工作负载。


很好的解释,用于理解事件循环同时适用于多个请求。
A
Aman Gupta

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

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

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


O
OfirD

添加到 slebetman 的答案中,以便更清楚地了解执行代码时会发生什么。

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

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

NextTick 队列 微任务队列 Timers 队列 IO 回调队列(请求、文件操作、数据库操作) IO 轮询队列 Check Phase 队列或 SetImmediate 关闭处理程序队列

每当请求到来时,代码就会按照队列中的回调顺序执行。

它不像当有阻塞请求时它被附加到一个新线程。默认情况下只有 4 个线程。所以那里发生了另一个排队。

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

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


nodeJs 中的内部线程池默认只有 4 个线程……你能详细说明一下吗?节点如何使用 4 个线程?
NodeJs 内部使用几个线程(默认为 4 个)用于连接数据库、从磁盘读取文件、与操作系统交互等。您可以通过使用环境变量“UV_THREADPOOL_SIZE”将其设置为不同的来增加此线程数运行您的 nodejs 应用程序时的编号。
A
Anurag Vohra

多线程阻塞系统的阻塞部分使其效率降低。被阻塞的线程在等待响应时不能用于其他任何事情。

而非阻塞单线程系统则充分利用了它的单线程系统。

https://i.stack.imgur.com/yLA5K.png

让我们看看非阻塞是如何工作的:

https://i.stack.imgur.com/EtiYI.png

这就是事件循环在 NodeJS 中的工作方式,并且比阻塞多线程系统执行得更好。


如果客户端没有自己的线程,它如何不超时?是否有某种将回调添加到的队列?如果代码设计得好并且不会阻塞事件循环,那么这个队列就会很快完成。
@MattieG4 客户端在通过回调后继续做其他事情。现在它的 callack 接收器的工作是在工作完成后调用回调。是的,事件循环具有队列结构,其中任务被添加并以 FIFO 为基础提供服务。如果队列中有任何阻塞代码,它将延迟队列中的每个项目/任务。
a
asif.ibtihaj

以下是来自 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 操作这一事实使其效率低下。使用这种模型,网络服务器只能处理与线程池中的线程一样多的请求。

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

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


“然后事件循环将自己处理请求并将响应发送回客户端。” <- 在这种情况下,cpu 在没有 I/O 启动的情况下处理请求,对吗?当使用 I/O 时,cpu 会启动 I/O 来完成工作,然后当 I/O 完成时,向 cpu 发送更新以发送回客户端?