通过Node.js进行的HTTP请求相比于浏览器会有延迟问题

15

我正在使用Node.js通过HTTP请求查询一些公共API。因此,我正在使用request模块。我在我的应用程序中测量响应时间,并发现我的应用程序返回API查询结果的速度比通过curl或浏览器进行的"直接"请求慢2-3倍。另外,我注意到连接到启用HTTPS的服务通常比纯HTTP连接需要更长的时间,但这可能是巧合。

我尝试优化了request选项,但没有成功。例如,我查询

https://www.linkedin.com/countserv/count/share?url=http%3A%2F%2Fwww.google.com%2F&lang=en_US

我正在使用request.defaults设置所有请求的默认值:

var baseRequest = request.defaults({
    pool: {maxSockets: Infinity},
    jar: true,
    json: true,
    timeout: 5000,
    gzip: true,
    headers: {
        'Content-Type': 'application/json'
    }
});

实际的请求是通过

...
var start = new Date().getTime();

var options = {
    url: 'https://www.linkedin.com/countserv/count/share?url=http%3A%2F%2Fwww.google.com%2F&lang=en_US',
    method: 'GET'
};

baseRequest(options, function(error, response, body) {

    if (error) {
        console.log(error);
    } else {
        console.log((new Date().getTime()-start) + ": " + response.statusCode);
    }

});

有人看到优化潜力吗?我做错了什么吗?提前感谢任何建议!


你是从你的Node.js代码中发出请求,还是从同一台机器上的curl请求? - Tristan Foureur
@TristanFoureur 是的,我是这么认为的。我认为这种行为可能是由于某些请求选项引起的,但我似乎找不到选择哪些选项以获得最佳性能的方法。 - Tobi
我刚刚尝试了你的代码,没有做任何更改。使用你的代码平均响应时间为545毫秒,而使用多个curl调用的平均响应时间为550毫秒。 - Tristan Foureur
为了提供更多细节,我正在运行多个工作进程,用于通过基于RabbitMQ的分布式RPC系统向公共API发送HTTP请求。这意味着每个节点进程可能会有数百个并发的“打开”请求。在高负载下我看到了延迟,简单(低数量的)请求正常工作...所以,我猜测可能需要对请求选项进行一些调整... - Tobi
那么你可能想要看一下 hyperquest,它可能对你有所帮助。此外,个人在需要执行大量请求的情况下,会使用一些作业队列和 X 个 worker,以确保保持在 X 个并发请求以下。 - Tristan Foureur
乍一看,这看起来很有前途...肯定会去试试。谢谢! - Tobi
2个回答

12

根据我理解你的架构,你需要解决几个潜在的问题,它们没有特定的顺序:

  • 使用request将始终比直接使用http慢,因为正如有智慧的人曾经说过的:“抽象成本高”。;)实际上,为了挤出尽可能多的性能,我会使用节点的net模块直接处理所有HTTP请求。对于HTTPS,重写https模块是不值得的。记录一下,由于需要握手加密密钥并对有效负载进行加密/解密处理,HTTPS在定义上总是比HTTP慢。
  • 如果你的要求包括从任何单个服务器检索多个资源,请确保使用http KeepAlive按顺序进行请求,以便从已打开的套接字中受益。与在已打开的套接字上进行请求相比,重新握手新的TCP套接字所需的时间巨大。
  • 确保禁用http连接池(请参见Nodejs Max Socket Pooling Settings
  • 确保你的操作系统和shell没有限制可用套接字的数量。有关提示,请参见How many socket connections possible?
  • 如果你正在使用Linux,请检查Increasing the maximum number of tcp/ip connections in linux,我还强烈建议微调内核套接字缓冲区。

如果我想到更多建议,我会添加它们。

更新

关于从同一端点进行多个请求的更多信息:

如果需要从同一端点检索多个资源,则将请求分段到特定的工作者中,这些工作者维护与该端点的开放连接将很有用。以这种方式,您可以确保在没有初始TCP握手的开销的情况下尽快获得所请求的资源。

TCP握手是一个三阶段的过程。

第一步:客户端向远程服务器发送一个SYN数据包。 第二步:远程服务器用SYN+ACK回复客户端。 第三步:客户端通过ACK回复远程服务器。

这取决于客户端到远程服务器的延迟,这可能会增加(正如William Proxmire曾经所说的)“真钱”,在这种情况下,是延迟。

从我的桌面电脑上,对于2K八位组包到www.google.com的当前延迟(由ping测量得出),任何地方都在37至227毫秒之间。

因此假设我们可以依赖于平均往返时间为95ms(在完美的连接上),建立初始TCP握手需要约130ms或SYN(45ms)+ SYN+ACK(45ms)+ ACK(45ms),这只是为了建立初始连接而需要的十分之一秒。

如果连接需要重传,这可能需要 更长 的时间。

这还假定您通过新的TCP连接检索单个资源。

为了改善这种情况,我建议您的工作进程保持到“已知”目标的开放连接池,然后将其广告回监督进程,以便它可以将请求定向到具有“活”连接的最不繁忙的服务器。


哇,非常感谢您详尽的回答。我会尝试在周末进行测试。 - Tobi
感谢你的回答。我认为 Keep-Alive 头文件可能会产生最大的影响,除了禁用 Node 中的 HTTP 连接池之外。不幸的是,我无法按所请求的端点对请求进行分区,因为我想通过 RabbitMQ 在我的 RPC 工作程序上轮流使用循环调度。但这是个好主意! - Tobi

5

实际上,我有一些足够好的新元素可以提供一个真正的答案。看一下request如何使用HTTP代理,请尝试以下操作:

var baseRequest = request.defaults({
    pool: false,
    agent: false,
    jar: true,
    json: true,
    timeout: 5000,
    gzip: true,
    headers: {
        'Content-Type': 'application/json'
    }
});

这将禁用连接池并使其更快。


谢谢,我会尝试一下! - Tobi
你说得对,这是一个在大量请求中整体速度上进行权衡的问题,这就是为什么我只要求他尝试一下并观察它在他特定的使用情况下的表现。 - Tristan Foureur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接