【发布时间】:2016-05-25 01:24:14
【问题描述】:
我正在使用request 模块与async 模块的组合同时抓取大量链接。
我注意到很多 ETIMEDOUT 和 ESOCKETTIMEDOUT 错误,尽管链接是可访问的并且使用 chrome 可以快速响应。
我在请求选项中将 maxSockets 限制为 2,将 timeout 限制为 10000。
我使用async.filterLimit(),限制为2,甚至每次将并行度减少到2个请求。
所以我有 2 个套接字、2 个请求和 10 秒的超时时间来等待来自服务器的标头响应,但我得到了这些错误。
这是我使用的请求配置:
{
...
pool: {
maxSockets: 2
},
timeout: 10000
,
time: true
...
}
这是我用来验证链接的代码的 sn-p:
var self = this;
async.filterLimit(resources, 2, function(resource, callback) {
request({
uri: resource.uri
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
...
} else {
self.emit('error', resource, error);
}
callback(...);
})
}, function(result) {
callback(null, result);
});
我听了错误事件,每当错误代码为 ETIMEDOUT 时,我看到连接对象是真/假,所以有时是连接超时,有时不是(根据请求文档)
更新:
我决定将maxSockets 提升到Infinity,这样就不会因为缺少可用的套接字而挂断连接:
pool: {
maxSockets: Infinity
}
为了控制带宽,我实现了一个requestLoop 方法,该方法使用maxAttemps 和retryDelay 参数来控制请求:
async.filterLimit(resources, 10, function(resource, callback) {
self.requestLoop({
uri: resource.uri
}, 100, 5000, function (error, response, body) {
var fetched = false;
if (!error) {
...
} else {
....
}
callback(...);
});
}, function(result) {
callback(null, result);
});
requestLoop的实现:
requestLoop = function(options, attemptsLeft, retryDelay, callback, lastError) {
var self = this;
if (attemptsLeft <= 0) {
callback((lastError != null ? lastError : new Error('...')));
} else {
request(options, function (error, response, body) {
var recoverableErrors = ['ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED'];
var e;
if ((error && _.contains(recoverableErrors, error.code)) || (response && (500 <= response.statusCode && response.statusCode < 600))) {
e = error ? new Error('...');
e.code = error ? error.code : response.statusCode;
setTimeout((function () {
self.requestLoop(options, --attemptsLeft, retryDelay, callback, e);
}), retryDelay);
} else if (!error && (200 <= response.statusCode && response.statusCode < 300)) {
callback(null, response, body);
} else if (error) {
e = new Error('...');
e.code = error.code;
callback(e);
} else {
e = new Error('...');
e.code = response.statusCode;
callback(e);
}
});
}
};
所以总结一下:
- 将maxSockets 提升到Infinity 以尝试克服套接字连接的超时错误
- 实现requestLoop 方法来控制失败的请求和maxAttemps 以及此类请求的retryDelay
- 还有最大并发请求数由传递给async.filterLimit的数量设置
我想指出,我也尝试过这里所有的设置,以便无错误地抓取,但到目前为止尝试也失败了。
仍在寻求有关解决此问题的帮助。
更新 2:
我决定放弃 async.filterLimit 并建立自己的限制机制。
我只有 3 个变量来帮助我实现这一目标:pendingRequests - 一个包含所有请求的请求数组(稍后会解释)
activeRequests - 活动请求数
maxConcurrentRequests - 允许的最大并发请求数
到 pendingRequests 数组中,我推送一个复杂对象,其中包含对 requestLoop 函数的引用以及包含要传递给循环函数的参数的 arguments 数组:
self.pendingRequests.push({
"arguments": [{
uri: resource.uri.toString()
}, self.maxAttempts, function (error, response, body) {
if (!error) {
if (self.policyChecker.isMimeTypeAllowed((response.headers['content-type'] || '').split(';')[0]) &&
self.policyChecker.isFileSizeAllowed(body)) {
self.totalBytesFetched += body.length;
resource.content = self.decodeBuffer(body, response.headers["content-type"] || '', resource);
callback(null, resource);
} else {
self.fetchedUris.splice(self.fetchedUris.indexOf(resource.uri.toString()), 1);
callback(new Error('Fetch failed because a mime-type is not allowed or file size is bigger than permited'));
}
} else {
self.fetchedUris.splice(self.fetchedUris.indexOf(resource.uri.toString()), 1);
callback(error);
}
self.activeRequests--;
self.runRequest();
}],
"function": self.requestLoop
});
self.runRequest();
您会注意到最后对runRequest() 的调用。
此功能的工作是管理请求并在可能的情况下触发请求,同时将最大 activeRequests 保持在 maxConcurrentRequests 的限制以下:
var self = this;
process.nextTick(function() {
var next;
if (!self.pendingRequests.length || self.activeRequests >= self.maxConcurrentRequests) {
return;
}
self.activeRequests++;
next = self.pendingRequests.shift();
next["function"].apply(self, next["arguments"]);
self.runRequest();
});
这应该可以解决任何超时错误,通过我的测试,我仍然注意到我测试过的特定网站中的一些超时。我不能 100% 确定这一点,但我认为这是由于支持 http-server 的网站的性质,通过进行 ip-checking 将用户请求限制在最大,结果返回一些 HTTP 400 消息以防止对服务器的可能“攻击”。
【问题讨论】:
-
你有没有想过@Jorayen?
-
@DvideBy0 更新了解决方案
-
我发现我的代码问题略有不同。就底层套接字而言,似乎没有尊重在我的代码中设置超时。我不得不等待套接字事件(见下文)
-
.on('socket', function(socket) { socket.setTimeout(30000); socket.on('timeout', function() { request.abort(); logger.error('请求在 30 秒后超时'); }); })
标签: node.js sockets asynchronous request httprequest