增加从 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" 模块相反的不同队列机制中。当然还要感谢多任务处理。
我有一个简单的案例,我从我的 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" 模块相反的不同队列机制中。当然还要感谢多任务处理。