节点如何处理并发请求? [英] How does node process concurrent requests?

查看:110
本文介绍了节点如何处理并发请求?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近一直在阅读nodejs,试图理解它如何处理多个并发请求,我知道nodejs是一个基于单线程事件循环的架构,在给定的时间点只有一个语句将被执行,即在主线程和阻塞代码/ IO调用由工作线程处理(默认为4)。

I have been reading nodejs lately, trying to understand how it handles multiple concurrent requests, I know nodejs is a single threaded event loop based architecture, at a given point of time only one statement is gonna execute i.e, on main thread and blocking code/IO calls are handled by the worker threads (default is 4).

现在我的问题是当使用nodejs构建的Web服务器收到多个请求时会发生什么,I知道,有很多Stack溢出线程有类似的问题,但没有找到具体的答案。

Now my question is what happens when a web server built using nodejs receives multiple requests, I know, there are a lots of Stack overflow thread that has similar questions, but didn't find a concrete answer to this.

所以我在这里举一个例子,假设我们在 / index 之类的路线中有以下代码。

So I am putting an example here, let's say we have following code inside a route like /index.


app.use('/index', function(req, res, next) {

    console.log("hello index routes was invoked");

    readImage("path", function(err, content) {
        status = "Success";
        if(err) {
            console.log("err :", err);
            status = "Error"
        }
        else {
            console.log("Image read");
        }
        return res.send({ status: status });
    });

    var a = 4, b = 5;
    console.log("sum =", a + b);
});


我们假设readImage()需要大约1分钟才能读取图片。
如果两个请求T1和T2来得很清楚,nodejs将如何处理这些请求?

Let's assume that readImage() takes around 1 min to read that Image. If two request T1, and T2 came concurently, How nodejs is gonna process these request ?

它是否会采取第一个请求T1,在排队时处理它请求T2 (如果我的理解是错误的,请纠正我),如果遇到像readImage那样的任何异步/阻塞内容,它会将其发送到工作线程(稍后当异步内容完成时)通知主线程和主线程开始执行回调),通过执​​行下一行代码继续前进。
完成T1后,然后选择T2请求?这是对的吗?或者它可以处理之间的T2代码(意味着在调用readImage时,它可以开始处理T2)?

Does it going to take first request T1, process it while queueing the request T2 (please correct me if my understanding is wrong here), if any async/blocking stuff is encountered like readImage, it then sends that to worker thread (some point later when async stuff is done it notifies the main thread and main thread starts executing the callback), move ahead by executing the next line of code. When it is done with T1 then picks T2 request ? Is it correct? or it can process T2 code in between (meaning while readImage is called, it can start processing T2)?

如果有人能帮我找到答案,我真的很感激这个问题

I would really appreciate if anyone can help me finding an answer to this question

推荐答案

你的困惑可能来自于没有足够关注事件循环。很明显你知道这是如何工作的,但也许不是全貌。

Your confusion might be coming from not focusing on the event loop enough. clearly you have an idea of how this works, but maybe not the full picture.

当您调用使用方法时,幕后发生的是创建另一个线程来监听连接。

When you call the use method, what happens behind the scenes is another thread is created to listen for connections.

然而,当一个请求进来时,因为我们在与V8引擎不同的线程中(并且不能直接调用路由函数),对该函数的序列化调用将附加到共享事件循环,以便稍后调用。 (事件循环在这个上下文中是一个很差的名字,因为它更像是一个队列或堆栈)

However, when a request comes in, because we're in a different thread than the V8 engine (and cannot directly invoke the route function), a serialized call to the function is appended onto the shared event loop, for it to be called later. (event loop is a poor name in this context, as it operates more like a queue or stack)

在js文件的末尾,V8会检查是否有任何在事件循环中运行theads或消息。如果没有,它将退出0(这就是服务器代码保持进程运行的原因)。因此要理解的第一个时序细微差别是在达到js文件的同步结束之前不会处理任何请求。

at the end of the js file, V8 will check if there are any running theads or messages in the event loop. If there are none, it will exit 0 (this is why server code keeps the process running). So the first Timing nuance to understand is that no request will be processed until the synchronous end of the js file is reached.

如果事件循环被附加到进程启动时,事件循环上的每个函数调用将被完整地同步处理。

If the event loop was appended to while the process was starting up, each function call on the event loop will be handled one by one, in its entirety, synchronously.

为简单起见,让我把你的例子分解成更具表现力的东西。

For simplicity, let me break down your example into something more expressive.

function callback() {
    setTimeout(function inner() {
        console.log('hello inner!');
    }, 0); // †
    console.log('hello callback!');
}

setTimeout(callback, 0);
setTimeout(callback, 0);

setTimeout 带时间0,是一种快速简便的方法,可以在事件循环中放置一些没有任何计时器复杂的东西,因为无论如何,它始终至少为0ms。

setTimeout with a time of 0, is a quick and easy way to put something on the event loop without any timer complications, since no matter what, it has always been at least 0ms.

在此示例中,输出将始终为:

In this example, the output will always be:

hello callback!
hello callback!
hello inner!
hello inner!

回调的序列化调用都附加到在调用其中任何一个之前的事件循环,保证。发生这种情况是因为在完全同步执行文件之后才能从事件循环中调用任何内容。

Both serialized calls to callback are appended to the event loop before either of them is called, guaranteed. This happens because nothing can be invoked from the event loop until after the full synchronous execution of the file.

考虑执行您的执行情况会很有帮助file,作为事件循环的第一件事。因为事件循环中的每次调用只能串行发生,所以它成为一个逻辑结果,在执行过程中不会发生其他事件循环调用;只有当它完成时,才能调用另一个事件循环函数。

同样的逻辑同样适用于内部回调,可用于解释程序永远不会输出的原因:

The same logic applies to the inner callback as well, and can be used to explain why the program will never output:

hello callback!
hello inner!
hello callback!
hello inner!

就像你期望的那样。

By在文件执行结束时,2个序列化函数调用将在事件循环中,两者都用于回调。由于事件循环是 FIFO (先进先出), setTimeout 首先会被调用。

By the end of the execution of the file, 2 serialized function calls will be on the event loop, both for callback. As the Event loop is a FIFO (first in, first out), the setTimeout that came first, will be be invoked first.

第一件事回调是执行另一个 setTimeout 。和以前一样,这会向事件循环追加一个序列化的调用,这次到内部函数 setTimeout 立即返回,执行将转移到第一个 console.log

The first thing callback does is perform another setTimeout. As before, this will append a serialized call, this time to the inner function, to the event loop. setTimeout immediately returns, and execution will move on to the first console.log.

此时,事件循环如下所示:

At this time, the event loop looks like this:

1 [callback] (executing)
2 [callback] (next in line)
3 [inner]    (just added by callback)

返回回调是事件循环从其自身中删除该调用的信号。这会在事件循环中留下2件事:1次调用回调,1次调用内部

The return of callback is the signal for the event loop to remove that invocation from itself. This leaves 2 things on the event loop now: 1 more call to callback, and 1 call to inner.

callback 是下一个函数,所以接下来会调用它。这个过程重演。对 inner 的调用将附加到事件循环中。一个 console.log 打印 Hello Callback!,我们完成了删除回调<的调用/ code>来自事件循环。

callback is the next function in line, so it will be invoked next. The process repeats itself. A call to inner is appended to the event loop. A console.log prints Hello Callback! and we finish by removing this invocation of callback from the event loop.

这为事件循环留下了另外两个函数:

This leaves the event loop with 2 more functions:

1 [inner]    (next in line)
2 [inner]    (added by most recent callback)

这些函数都没有进一步混乱事件循环,它们一个接一个地执行;第二个等待第一个回归。然后当第二个返回时,事件循环保持为空。这结合了当前没有其他线程正在运行的事实,触发了该过程的结束。退出0。

Neither of these functions mess with the event loop any farther, they execute one after the other; The second one waiting for the first one's return. Then when the second one returns, The event loop is left empty. This combined wit the fact that there are no other threads currently running, triggers the end of the process. exit 0.

在您的示例中发生的第一件事,是在进程内创建一个线程,它将创建一个绑定到特定端口的服务器。注意,这发生在预编译的C ++中,而不是javascript,并且不是一个单独的进程,它是同一进程中的一个线程。 参见: C ++线程教程

The first thing that happens in your example, is that a thread is created, within the process, that will create a server bound to a particular port. Note, this is happening in precompiled C++, not javascript, and is not a separate process, its a thread within the same process. see: C++ Thread Tutorial

现在,只要有请求进入,原始代码的执行就不会受到干扰。相反,传入的连接请求将被打开,保持并附加到事件循环。

So now, whenever a request comes in, the execution of your original code wont be disturbed. Instead, incoming connection requests will be opened, held onto, and appended to the event loop.

使用功能,是捕获传入请求事件的网关。它是一个抽象层,但为了简单起见,它有助于想到使用函数,就像你将 setTimeout 。除了等待一段时间之外,它会在传入的http请求时将回调附加到事件循环。

The use function, is the gateway into catching the events for incoming requests. Its an abstraction layer, but for the sake of simplicity, Its helpful to think of the use function like you would a setTimeout. Except, instead of waiting a set amount of time, it appends the callback to the event loop upon incoming http requests.

所以,我们假设有两个请求进入服务器:T1和T2。在你的问题中,你说它们同时进入,因为这在技术上是不可能的,我会假设它们是一个接一个,它们之间的时间可以忽略不计。

So, lets assume that there are two requests coming in to the server: T1 and T2. In your question you say they come in concurrently, since this is technically impossible, I'm going to assume they are one after the other, with a negligible time in between them.

无论哪个请求首先出现,都将由先前的辅助线程首先处理。一旦打开了该连接,它就会附加到事件循环中,然后我们转到下一个请求,然后重复。

Whichever request comes in first, will be handled first by the secondary thread from earlier. once that connection has been opened, its appended to the event loop, and we move on to the next request, and repeat.

在添加第一个请求后的任何时候到事件循环,V8可以开始执行使用回调。

At any point after the first request is added to the event loop, V8 can begin execution of the use callback.

由于不清楚 readImage 是否来自某个特定的库,你呢?写或其他方式,在这种情况下不可能准确地说出它会做什么。但是只有两种可能性,所以这里它们是:

Since its unclear whether readImage is from a particular library,, something you wrote or otherwise, its impossible to tell exactly what it will do in this case. There are only 2 possibilities though, so here they are:

// in this example definition of readImage, its entirely
// synchronous, never using an alternate thread or the
// event loop
function readImage (path, callback) {
    let image = fs.readFileSync(path);
    callback(null, image);
    // a definition like this will force the callback to
    // fully return before readImage returns. This means
    // means readImage will block any subsequent calls.
}

// in this alternate example definition its entirely
// asynchronous, and take advantage of fs' async
// callback.
function readImage (path, callback) {
    fs.readFile(path, (err, data) => {
        callback(err, data);
    });
    // a definition like this will force the readImage
    // to immediately return, and allow exectution
    // to continue.
}

出于解释的目的,我将在readImage的假设下运作将立即返回,因为正确的异步函数应该。

For the purposes of explanation, I'll be operating under the assumption that readImage will immediately return, as proper asynchronous functions should.

一旦使用开始回调执行,将发生以下情况:

Once the use callback execution is started, the following will happen:


  1. 将打印第一个控制台日志。

  2. readImage将启动一个工作线程并立即返回。

  3. 将打印第二个控制台日志。

在所有这一切中,重要的是要注意,这些操作是同步进行的;在这些事件完成之前,没有其他事件循环调用可以启动。 readImage可能是异步的,但调用它不是,工作线程的回调和使用是使它异步的原因。

在此<$ c $之后c>使用回调返回,下一个请求可能已经完成解析并被添加到事件循环中,而V8正在忙着我们的控制台日志和readImage调用。

After this use callback returns, the next request has probably already finished parsing and was added to the event loop, while V8 was busy doing our console logs and readImage call.

所以下一个使用回调被调用,并重复相同的过程:log,启动readImage线程,再次登录,返回。

So the next use callback is invoked, and repeats the same process: log, kick off a readImage thread, log again, return.

在此之后,读取图像(取决于它们花费多长时间)可能已经检索了他们需要的内容并将其回调附加到事件循环。因此,它们将按照先检索其数据的顺序执行。记住,这些操作是在不同的线程中发生的,所以这不仅与主javascript线程并行,而且彼此平行,所以在这里,首先调用哪一个并不重要,哪个先完成,然后得到了事件循环中的dibs。

After this point, the read Images (depending on how long they take) have probably already retrieved what they needed and appended their callback to the event loop. So they will get executed next, in order of whichever one retrieved its data first. remember, These operations were happening in separate threads, so the happened not only parallel to the main javascript thread, but also parallel to each other, so here, it doesnt matter which one got called first, it matters which one finished first, and got dibs on the event loop.

首先完成的readImage将是第一个执行的。因此,假设没有错误,我们将打印到控制台,然后写入相应请求的响应,保存在词法范围内。

Whichever readImage completed first will be the first one to execute. so, assuming no errors, we'll print out to the console, then write to the response for the corresponding request, held in lexical scope.

当send返回时,下一个readImage回调将开始执行:console log,并写入响应。

When that send returns, the next readImage callback will begin execution: console log, and writing to the response.

此时,两个readImage线程都已经死亡,并且事件循环为空,但保存服务器端口绑定的线程使进程保持活动状态,等待其他东西添加到事件循环,并继续循环。

at this point, both readImage threads have died, and the event loop is empty, but the thread that holds the server port binding is keeping the process alive, waiting for something else to add to the event loop, and the cycle to continue.

我希望这可以帮助您理解您提供的示例异步性质背后的机制

I hope this helps you understand the mechanics behind the asynchronous nature of the example you provided

这篇关于节点如何处理并发请求?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆