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

时间:2015-04-07 07:03:51

标签: javascript node.js node-async

我有一个简单的例子,我从node.js服务器请求不同的上游代理服务器。随着负载的增加,我看到请求需要花费大量的时间来执行(尽管我的上游代理服务器响应的时间在请求中是不变的)。为了证明这个问题,我已经编写了一个示例程序,如下所示。当我执行以下程序时,第一个请求执行 118ms ,最后一个请求 10970ms ,具体取决于您点击的网站(我已将网址更改为谷歌,用你最喜欢的网站试试吧)。如果您发现我使用异步来并行化我的请求。

问题是,node.js在并行运行时花了这么多时间来执行请求的原因是什么。为了给出更多关于infra设置的上下文(centos 6.5),我打开了1024到65535的端口范围,将fin_timeout更改为15秒,并为sysctl.conf中的套接字启用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

2 个答案:

答案 0 :(得分:3)

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

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'

在节点0.10上,默认值为5;在节点0.12上,默认值为Infinity(&#34;无限制&#34;)。因此,如果您不在节点0.12上,则应在代码中增加该值:

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

答案 1 :(得分:2)

我尝试使用JXcore(Node.JS的分支,以及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个请求与JXcore一起运行对我来说快一点(我能够在我的平台上测量高达20%的增益)。这可能因机器而异,因为多任务处理在一个进程中使用不同的线程/实例(不再需要集群)。我的机器只有4个线程,这就是我使用jxcore.tasks.setThreadCount(4);的原因。您可以尝试使用32:)

处理每个请求的方式没有显着差异,所以我并不是说每个请求花费的时间更少,但是密钥可能隐藏在不同的排队机制中,与#34; async&#34相反;模块。当然,多亏了多任务处理。