增加从 node.js 到外部系统的并行请求需要更多时间来响应

Increase of parallel requests form node.js to external system takes more time to respond

我有一个简单的案例,我从我的 node.js 服务器请求不同的上游代理服务器。随着负载的增加,我看到请求需要花费大量时间来执行(尽管从我的上游代理服务器响应所花费的时间在请求中是恒定的)。为了演示这个问题,我编写了一个示例程序,如下所示。当我执行下面的程序时,第一个请求需要 118ms 来执行,最后一个请求需要 10970ms 取决于你访问的网站(我已经将 url 更改为 google,用你最喜欢的网站试试)。如果您观察到我正在使用异步来并行化我的请求。

问题是,当运行并行执行请求时,node.js需要这么多时间的原因是什么。为了提供更多关于基础设施设置(centos 6.5)的上下文,我打开了从 1024 到 65535 的端口范围,将 fin_timeout 更改为 15 秒并为 [=27] 中的套接字启用 tw_reuse =1 =]

var http = require('http');
var uuid = require('node-uuid');
var async = require('async');

function callExternalUrl(){
    var uniqueId = uuid.v4();
    console.time(uniqueId);
    var options = {
        host: 'google.com',
        port: '80',
        path: '/',
        method: 'GET'
    };
    var req = http.request(options, function(res) {
        var msg = '';
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            msg += chunk;
            console.timeEnd(uniqueId);
        });
        res.on('end', function() {
        });
    });
    req.end();
}

function iterateAsync(callback){
    var iter = [];
    for(var i=0; i<1000; i++){
        iter[i] = i;
    }
    async.each(iter,
        function(item, callback) {
            callExternalUrl();
        },
        function(err) {
            callback(err);
        }
    );
}

iterateAsync(function(){console.log('done');});  

为了提供更多上下文,下面是 ruby 中用于执行相同操作的代码。我知道我无法将这两种语言进行比较,就像苹果对苹果一样。但想法是显示使用 ruby 按顺序执行相同请求所需的时间。我没有看到按顺序发出的每个请求的响应时间有任何增加。所以,我怀疑使用节点的并行请求是否需要更多时间来响应请求(问题不是来自服务器响应,而是来自机器本身发出的请求)

require 'rest_client'

 1000.times do |number|
   beginning = Time.now
   response = RestClient.get 'http://google.com'
   puts "Time elapsed #{Time.now - beginning} seconds"
 end

首先,您没有调用异步迭代器回调函数:

function callExternalUrl(asyncCallback) {
  ...
  res.on('end', function() {
    asyncCallback();
  });
  ...
}

function iterateAsync(callback) {
  var iter = [];
  for(var i=0; i<1000; i++){
      iter[i] = i;
  }
  async.each(iter,
      function(item, asyncCallback) { // <-- HERE
        callExternalUrl(asyncCallback);
      },
      function(err) {
        callback(err);
      }
  );
}

此外,根据您使用的节点版本,http 模块可能会限制对特定主机名发出的并行请求的数量:

$ node -pe 'require("http").globalAgent.maxSockets'

在 Node 0.10 上,默认为 5;在 Node 0.12 上,默认值为 Infinity ("unlimited")。因此,如果您不在 Node 0.12 上,则应在代码中增加该值:

var http = require('http');
http.globalAgent.maxSockets = Infinity;
...

我已经尝试 运行 您的方案,方法是使用 JXcore (fork of Node.JS, and an open source project now on github),它提供多任务处理(以及许多其他新功能)。

var task = function (item) {
  var http = require('http');
  var uuid = require('node-uuid');

  var uniqueId = uuid.v4() + "-" + process.threadId;
  console.time(uniqueId);
  var options = {
    host: 'google.com',
    port: '80',
    path: '/',
    method: 'GET'
  };
  var req = http.request(options, function (res) {
    var msg = '';
    res.setEncoding('utf8');
    res.on('data', function (chunk) {
      msg += chunk;
      console.timeEnd(uniqueId);
    });
    res.on('end', function () {
      process.release();
    });
  });
  req.end();
  process.keepAlive();
};

jxcore.tasks.setThreadCount(4);
console.time("total");
process.on('exit', function () {
  console.timeEnd("total");
});

for (var i = 0; i < 1000; i++)
  jxcore.tasks.addTask(task, i);

样本并没有真正优化,但总的 1000 个请求 运行s 对我来说 JXcore 稍微快了一点(我能够在我的平台上测量高达 20% 的增益)。这可能因机器而异,因为多任务处理在一个进程中使用不同的 threads/instances(不再需要集群)。我的机器只有 4 个线程,这就是我使用 jxcore.tasks.setThreadCount(4); 的原因。你可以试试你的 32 :)

处理每个请求的方式并没有太大的不同,所以我并不是说每个请求花费的时间更少,而是密钥可能隐藏在与 "async" 模块相反的不同队列机制中。当然还要感谢多任务处理。