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

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

还有,单线程能适应这么大的数量吗?请记住,我刚刚开始学习Node.js。


当前回答

在node.js中,请求应该是IO绑定,而不是CPU绑定。这意味着每个请求不应该强迫node.js做大量的计算。如果在解决请求时涉及大量计算,那么node.js不是一个好的选择。IO界需要很少的计算量。请求的大部分时间都花在对DB或服务的调用上。

Node.js有单线程事件循环,但它只是一个厨师。在后台,大部分工作是由操作系统完成的,Libuv确保了与操作系统的通信。Libuv的文档如下:

在事件驱动编程中,应用程序表示感兴趣的 当某些事件发生时,对它们做出反应。的责任 从操作系统收集事件或监视其他 事件源由libuv处理,用户可以注册 事件发生时调用的回调。

The incoming requests are handled by the Operating system. This is pretty much correct for almost all servers based on request-response model. Incoming network calls are queued in OS Non-blocking IO queue.'Event Loop constantly polls OS IO queue that is how it gets to know about the incoming client request. "Polling" means checking the status of some resource at a regular interval. If there are any incoming requests, evnet loop will take that request, it will execute that synchronously. while executing if there is any async call (i.e setTimeout), it will be put into the callback queue. After the event loop finishes executing sync calls, it can poll the callbacks, if it finds a callback that needs to be executed, it will execute that callback. then it will poll for any incoming request. If you check the node.js docs there is this image:

从文档阶段概述

poll:检索新的I/O事件;执行I/O相关的回调(几乎 除了关闭回调,由 定时器,和setimmediation ());节点将在适当的时候阻塞在这里。

事件循环不断地从不同队列轮询。如果一个请求需要外部调用或磁盘访问,这将被传递给操作系统,操作系统也有2个不同的队列。一旦事件循环检测到某些事情必须异步完成,它就会将它们放入队列中。一旦它被放入队列中,事件循环将处理到下一个任务。

这里要提到的一件事是,事件循环持续运行。只有Cpu可以将这个线程移出Cpu,事件循环本身不会这样做。

从文档中可以看出:

The secret to the scalability of Node.js is that it uses a small number of threads to handle many clients. If Node.js can make do with fewer threads, then it can spend more of your system's time and memory working on clients rather than on paying space and time overheads for threads (memory, context-switching). But because Node.js has only a few threads, you must structure your application to use them wisely. Here's a good rule of thumb for keeping your Node.js server speedy: Node.js is fast when the work associated with each client at any given time is "small".

注意,小任务意味着IO绑定任务而不是CPU绑定任务。只有当每个请求的工作主要是IO工作时,单个事件循环才会处理客户机负载。

Context switch basically means CPU is out of resources so It needs to stop the execution of one process to allow another process to execute. OS first has to evict process1 so it will take this process from CPU and it will save this process in the main memory. Next, OS will restore process2 by loading process control block from memory and it will put it on the CPU for execution. Then process2 will start its execution. Between process1 ended and the process2 started, we have lost some time. Large number of threads can cause a heavily loaded system to spend precious cycles on thread scheduling and context switching, which adds latency and imposes limits on scalability and throughput.

其他回答

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

在内部,Node.JS的事件循环正在处理一个线程池,其中每个线程处理一个非阻塞请求,事件循环在将工作委派给线程池中的一个线程后继续侦听更多的请求。当其中一个线程完成工作时,它向事件循环发送一个信号,它已经完成,也就是回调。事件循环然后处理此回调并发回响应。

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

下面是来自这篇媒体文章的一个很好的解释:

给定一个NodeJS应用程序,由于Node是单线程的,假设处理涉及Promise。所有这些都需要8秒,这是否意味着在这个请求之后的客户端请求将需要等待8秒? 不。NodeJS事件循环是单线程的。NodeJS的整个服务器架构不是单线程的。

Before getting into the Node server architecture, to take a look at typical multithreaded request response model, the web server would have multiple threads and when concurrent requests get to the webserver, the webserver picks threadOne from the threadPool and threadOne processes requestOne and responds to clientOne and when the second request comes in, the web server picks up the second thread from the threadPool and picks up requestTwo and processes it and responds to clientTwo. threadOne is responsible for all kinds of operations that requestOne demanded including doing any blocking IO operations.

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

NodeJS Web Server maintains a limited Thread Pool to provide services to client requests. Multiple clients make multiple requests to the NodeJS server. NodeJS receives these requests and places them into the EventQueue . NodeJS server has an internal component referred to as the EventLoop which is an infinite loop that receives requests and processes them. This EventLoop is single threaded. In other words, EventLoop is the listener for the EventQueue. So, we have an event queue where the requests are being placed and we have an event loop listening to these requests in the event queue. What happens next? The listener(the event loop) processes the request and if it is able to process the request without needing any blocking IO operations, then the event loop would itself process the request and sends the response back to the client by itself. If the current request uses blocking IO operations, the event loop sees whether there are threads available in the thread pool, picks up one thread from the thread pool and assigns the particular request to the picked thread. That thread does the blocking IO operations and sends the response back to the event loop and once the response gets to the event loop, the event loop sends the response back to the client.

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

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

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

在“传统的”(非事件驱动的)应用程序体系结构中,流程花费大量时间等待某些事情发生。在基于事件的架构(如Node.js)中,进程不只是等待,它可以继续其他工作。

例如:你从客户端获得一个连接,你接受它,你读取请求头(在http的情况下),然后你开始对请求进行操作。您可能会读取请求体,通常会将一些数据发送回客户端(这是有意简化的过程,只是为了说明这一点)。

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

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

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

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

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

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

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

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

我不会说基于事件的系统在每种情况下或每种工作负载下都更快——它们在I/ o约束的工作负载上工作得很好,但在cpu约束的工作负载上就不那么好了。

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

The internal thread pool in nodeJs just has 4 threads by default. and its not like the whole request is attached to a new thread from the thread pool the whole execution of request happens just like any normal request (without any blocking task) , just that whenever a request has any long running or a heavy operation like db call ,a file operation or a http request the task is queued to the internal thread pool which is provided by libuv. And as nodeJs provides 4 threads in internal thread pool by default every 5th or next concurrent request waits until a thread is free and once these operations are over the callback is pushed to the callback queue. and is picked up by event loop and sends back the response.

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

NextTick队列 微任务队列 定时器队列 IO回调队列(请求,文件操作,数据库操作) IO轮询队列 检查Phase queue或setimmediation 关闭处理程序队列

每当有请求时,代码就按照这个回调队列的顺序执行。

它不像当有一个阻塞请求时,它会附加到一个新线程。默认情况下只有4个线程。这是另一个排队过程。

在代码中,无论何时发生像文件读取这样的阻塞进程,然后调用一个从线程池中利用线程的函数,一旦操作完成,回调被传递到各自的队列,然后按顺序执行。

所有内容都基于回调的类型进行排队,并按照上面提到的顺序进行处理。

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

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

见下图: 在这里,在厨房门口等待或在顾客挑选食物时等待,是“阻塞”了服务员的全部能力。在计算系统的意义上,它可以等待IO,或DB响应或任何阻塞整个线程的东西,即使线程在等待时能够进行其他工作。

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

在非阻塞系统中,服务员只接单和上菜,不在任何地方等待。他分享了他的手机号码,以便在他们完成订单后给他们回电话。同样地,他将自己的电话号码分享给厨房,以便在订单准备就绪时回复。

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