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

我有一个简单的情况,我正在从我的node.js服务器请求不同的上游代理服务器。 随着负载的增加,我看到请求花了很多时间来执行(尽pipe从我的上游代理服务器响应的时间在请求中是恒定的)。 为了演示这个问题,我写了一个示例程序如下。 当我执行下面的程序,第一个请求需要118毫秒执行和最后一个需要10970毫秒取决于你打的网站(我已经改变了url为谷歌,试试你最喜欢的网站)。 如果您观察到我正在使用asynchronous并发我的请求。

问题是 ,node.js在并行运行时需要花费这么多时间执行请求的原因什么? 为了给出更多的背景设置(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 

首先,你不调用asynchronous迭代器callback函数:

 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); } ); } 

而且,根据您使用的Node版本, http模块可能会限制对特定主机名执行的并行请求数:

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

在节点0.10上,默认值是5; 在节点0.12上,默认值是Infinity (“无限”)。 所以如果你不在Node 0.12上,你应该增加你的代码的值:

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

我试图通过使用JXcore ( Node.JS的 fork和github上的一个开源项目)来运行您的场景,它提供了多任务(在许多其他新function中)。

 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 🙂

处理每个请求的方式并没有太大的差别,所以我并不是说每个请求都需要较less的时间,但是密钥可能隐藏在与“asynchronous”模块相反的不同排队机制中。 当然也要感谢多任务处理。