【问题标题】:Child event order in Node.jsNode.js 中的子事件顺序
【发布时间】:2014-09-15 13:28:09
【问题描述】:

我有一个api,它的工作过程是这样的:

做一些逻辑,使用 1 秒的 CPU 时间

等待网络IO,这个IO也需要1秒。

所以,通常这个 api 需要大约 2 秒来响应

然后我做了一个测试。 我同时启动 10 个请求。 他们每个人都需要超过 10 秒的时间来响应

这个测试意味着 Node 将首先完成所有 10 个请求中的所有 cpu 开销较大的部分。

为什么? 为什么一个 IO 完成后不立即响应一个请求。


感谢 cmets。我想我需要对我的担忧做一些解释。

我关心的是如果请求数不是 10,如果同时有 100 个请求。 他们都会超时!!

如果Node立即响应子IO事件,我想至少有20%不会超时。

我认为节点需要一些事件优先级机制


router.use('/test/:id', function (req, res) {
    var id = req.param('id');
    console.log('start cpu code for ' + id);
    for (var x = 0; x < 10000; x++) {
        for (var x2 = 0; x2 < 30000; x2++) {
            x2 -= 1;
            x2 += 1;
        }
    }
    console.log('cpu code over for ' + id);
    request('http://terranotifier.duapp.com/wait3sec/' + id, function (a,b,data) {
        // how can I make this code run immediately after the server response to me.
        console.log('IO over for ' + data);
        res.send('over');
    });
});

【问题讨论】:

  • 如果您有一个“同步等待”I/O 的 node.js 应用程序,这是一个严重的设计问题。 node.js 应用程序必须异步处理所有 I/O 才能获得不错的性能。向我们展示您的实际代码,您可能会得到更好的帮助。
  • 网络 I/O 事件被添加到事件循环队列的末尾,在 10 个 CPU 密集型事件已经占据位置之后。它只需要等待轮到它。这就是所谓的“阻塞”——一个使事件循环明显忙碌并延迟处理其他事件的事件。
  • 感谢您的 cmets,我现在编辑问题。对我的担忧做了一些解释。
  • @JunnanWang 如果没有看到您的代码,或者至少是您如何处理请求的示例,就不可能为您提供帮助。您可能正在做一些事情来阻止线程。
  • 您在服务器代码中使用的是同步IO还是异步IO?正如我之前所说,在 node 中处理大量请求有非常好的策略,但这完全取决于你的代码。我们无能为力,但如果您不打算显示任何代码,请猜测并提出问题。

标签: node.js event-loop


【解决方案1】:

Node.js 是单线程的。因此,只要您有一个长时间运行的例程,它就无法处理其他代码段。在这种情况下,有问题的代码是你的双 for 循环,它占用了大量的 CPU 时间。

为了了解您首先看到的内容,让我解释一下事件循环的工作原理。

Node.js 事件循环是从 javascript 的事件循环演变而来的,而 javascript 的事件循环又是从 Web 浏览器事件循环演变而来的。 Web 浏览器事件循环最初不是为 javascript 实现的,而是允许渐进式渲染图像。事件循环看起来有点像这样:

,-> is there anything from the network?
|      |              |
|      no            yes
|      |              |
|      |              '-----------> read network data
|      V                                     |
|  does the DOM need updating? <-------------'
|      |              |
|      no            yes
|      |              |
|      |              v
|      |         update the DOM
|      |              |
'------'--------------'

添加 javascript 后,脚本处理被简单地插入到事件循环中:

,-> is there anything from the network?
|      |              |
|      no            yes
|      |              |
|      |              '-----------> read network data
|      V                                     |
|  any javascript to run? <------------------'
|      |              |
|      no            yes
|      |              '-----------> run javascript
|      V                                     |
|  does the DOM need updating? <-------------'
|      |              |
|      no            yes
|      |              |
|      |              v
|      |         update the DOM
|      |              |
'------'--------------'

当 javascript 引擎在浏览器之外运行时,如在 Node.js 中,DOM 相关部分被简单地删除,I/O 变得通用:

,-> any javascript to run?
|      |         |
|      no       yes
|      |         |
|      |         '--------> RUN JAVASCRIPT
|      V                         |
|  is there any I/O <------------'
|      |              |
|      no            yes
|      |              |
|      |              v
|      |          read I/O
|      |              |
'------'--------------'

请注意,您的所有 javascript 代码都在 RUN JAVASCRIPT 部分执行。

那么,当您建立 10 个连接时,您的代码会发生什么情况?

connection1: node accepts your request, processes the double for loops
connection2: node is still processing the for loops, the request gets queued
connection3: node is still processing the for loops, the request gets queued
(at some point the for loop for connection 1 finishes)
node notices that connection2 is queued so connection2 gets accepted,
process the double for loops
    ...
connection10: node is still processing the for loops, the request gets queued
(at this point node is still busy processing some other for loop,
 probably for connection 7 or something)
request1: node is still processing the for loops, the request gets queued
request2: node is still processing the for loops, the request gets queued
(at some point all connections for loops finishes)
node notices that response from request1 is queued so request1 gets processed,
console.log gets printed and res.send('over') gets executed.
    ...
request10: node is busy processing some other request, request10 gets queued
(at some point request10 gets executed)

这就是为什么您看到节点需要 10 秒来回答 10 个请求的原因。并不是请求本身很慢,而是它们的响应排在所有 for 循环之后,并且 for 循环首先执行(因为我们仍在事件循环的当前循环中)。

为了解决这个问题,您应该使 for 循环异步,让节点有机会处理事件循环。您可以用 C 编写它们并使用 C 为它们中的每一个运行独立的线程。或者,您可以使用 npm 中的线程模块之一在单独的线程中运行 javascript。或者你可以使用 worker-threads,它是一个为 Node.js 实现的类似于 API 的 web-worker。或者您可以分叉一组进程来执行它们。或者,如果并行性不重要,您可以简单地使用 setTimeout 循环它们:

router.use('/test/:id', function (req, res) {
 var id = req.param('id');
    console.log('start cpu code for ' + id);
    function async_loop (count, callback, done_callback) {
        if (count) {
            callback();
            setTimeout(function(){async_loop(count-1, callback)},1);
        }
        else if (done_callback) {
            done_callback();
        }
    }

    var outer_loop_done=0;
    var x2=0;
    async_loop(10000,function(){
        x1++;
        async_loop(30000,function(){
            x2++;
        },function() {
            if (outer_loop_done) {
                console.log('cpu code over for ' + id);
                request('http://terranotifier.duapp.com/wait3sec/' + id,
                    function (a,b,data){
                        console.log('IO over for ' + data);
                        res.send('over');
                    }
                );
            }
        });
    },function(){
        outer_loop_done = 1;
    });
});

上述代码将尽快处理来自request() 的响应,而不是等待所有async_loops 执行完成而不使用线程(因此没有并行性),而只是使用事件队列优先级。

【讨论】:

  • 谢谢,非常好的答案。看起来像是将 CPU 成本高昂的代码分解为一些“小事件”。我想我可以使用 e.emit 和 e.on 来替换内部循环来实现相同的目标。我说的对吗?
  • e.emit 只是执行挂起的事件。 e.on 只是排队等待事件的函数。您仍然需要定期为您调用 e.emit 以执行所有 e.on 。我知道的唯一与时间相关的事件发射器是 setTimeout 和 setInterval。您可以简单地将回调传递给它们。无需创建您自己的自定义事件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-12
  • 2014-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多