对 setTimeout 的并发调用导致比预期更多的延迟

Posted

技术标签:

【中文标题】对 setTimeout 的并发调用导致比预期更多的延迟【英文标题】:Concurrent calls to setTimeout cause more delays than expected 【发布时间】:2014-09-03 01:45:25 【问题描述】:

我正在做一个 Node 性能的小型演示,我很惊讶地看到以下示例对 setTimeout 进行 50 次并发调用需要超过 4 秒而不是 500 毫秒。

以下代码设置了一个非常简单的快速服务器,它监听所有请求,并在 500 毫秒后使用setTimeout 响应。然后它有一个客户端发出 50 个请求,传入一个数字来跟踪请求和相应的响应。

// SERVER
var express = require('express');
var app = express();

app.get('*', function (req, res) 
    setTimeout(function () 
        return res.send(req.query.i);
    , 500);
);

app.listen(8099);

// CLIENT
var http = require('http');

function start() 
    for (var i = 0; i < 50; i++) 
        console.log(new Date().getSeconds() + ':' + new Date().getMilliseconds() + ' - Sending ' + i);
        http.get('http://localhost:8099?i=' + i, responseHandler);
    

    function responseHandler(res) 
        res.on('data', function (body) 
            return console.log(new Date().getSeconds() + ':' + new Date().getMilliseconds() + ' - Received ' + body);
        );
    


start();

我预计代码需要 30 到 50 毫秒才能完成所有 setTimeout 调用,然后在 500 毫秒后所有这些调用将在同一时间左右响应。相反,我看到他们以 5 人一组的方式响应,每组 5 人之间有 500 毫秒。

我尝试了一些更简单的替代方案,它们都可以解决问题。如果我取出setTimeout 并立即回复,所有 50 条回复都会在 100 毫秒内收到。如果我将 Web 服务器排除在外,只对 setTimeout 进行 50 次调用,那么所有 50 次调用都会立即排队,并且所有 50 次调用会在 500 毫秒后同时返回。

当 express 和 http 调用与 setTimeout 结合使用时,可能导致额外延迟的原因是什么?

【问题讨论】:

您遇到了这些延迟because of the event queue。 我可以确认这些发现。亚历克斯,这不是一个有用的评论:everything 异步与事件队列相关。说这是“因为事件队列”就像说“这是因为编程”。无意义的。我怀疑 Node 中的计时器函数有某种“元队列”。也就是说,在计时器队列中,Node 获取接下来的五个可用计时器并安排这些计时器进行处理。不过不确定……现在查看 Node 源代码。 我收回我说的话:我不认为这是 setTimeout 的限制(因为,正如 Samuel 在他的帖子中所说,如果您在没有 HTTP 请求的情况下运行此测试,它会按预期工作)。所以这一定是 HTTP/Express 队列问题。 【参考方案1】:

这个问题与setTimeout 无关(当您在没有客户端/服务器设置的情况下运行测试时,您可能已经猜到了)。问题在于 Node http 服务器在任何给定时间将处理的打开 HTTP 套接字的数量。

控制它的设置是Agent.maxSockets(有关详细信息,请参阅documentation)。

maxSockets 的默认值为 5。因此,如果您向您的应用发送 50 个请求,它会一次处理其中的 5 个直到它们完成,然后再继续处理新的请求。由于在 setTimeout 函数调用它们的回调之前它们无法完成,因此您的请求会以大约每 500 毫秒 5 次的突发响应得到响应。

如果你想证明这一点,你所要做的就是改变maxSockets的值:

http.globalAgent.maxSockets = 10;

现在您将看到请求以 10 个突发的方式得到处理。如果您将其设置为 50,您应该会看到您正在寻找的行为。

请注意,它可能被设置为 5 有一个很好的理由,所以我不认为你的问题的“解决方案”只是将 maxSockets 设置为 Infinity 或类似的可怕的东西。

mscdex 在 cmets 中指出,您可以在 http.request 中指定一个 cusotm 代理,并且还会更改 Node v0.12 中的默认 maxSockets

【讨论】:

一些注意事项:应该更清楚地说明,您可以创建一个新的/自定义 Agent 实例,该实例具有自己的 maxSockets,您可以将其传递给 http.request() 而不是改变默认/全局代理的maxSockets。此外,默认/全局代理 maxSockets 不会再被节点 v0.12 限制(到 5 个)。 感谢您的信息,mscdex。我会更新我的答案。 完美。那解决了它。对于我的测试,我将其设置为 50。我了解最大套接字的需求,但 5 非常小,特别是如果您有一个 I/O 密集型应用程序,其中大部分时间都在等待大量 I/O 或外部进程。 您也可以随时使用集群来启动多个实例。

以上是关于对 setTimeout 的并发调用导致比预期更多的延迟的主要内容,如果未能解决你的问题,请参考以下文章

SQLite executemany 要求比预期更多的值

从两个表中选择比预期更多的行

加入两个查询会返回比预期更多的行?

查询中的 LEFT JOIN 返回比预期更多的记录

Oracle 过程分页带来比预期更多的行

为 __stdcall 函数指针提供比预期更多的参数