我正在使用request模块与async模块的组合并行抓取大量链接。
我注意到很多ETIMEDOUT
和ESOCKETTIMEDOUT
错误,但链接可以访问并使用chrome快速响应。
我在请求选项中将maxSockets
限制为2,将timeout
限制为10000。
我使用async.filterLimit()
的限制为2,甚至每次都将并行性降低到2个请求。
所以我有2个套接字,2个请求,以及10秒的超时等待来自服务器的标头响应,但我收到了这些错误。
这里;我使用的请求配置:
{
...
pool: {
maxSockets: 2
},
timeout: 10000
,
time: true
...
}
这是我用来制作链接的代码片段:
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
我想要注意的是,我还使用了此处所有设置的设置,以便免费抓取错误,但到目前为止尝试失败了。
仍在寻求解决此问题的帮助。
UPDATE2:
我决定放弃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();
您&#39;&#39;注意最后调用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();
});
这可以解决任何Timeout错误,通过我的测试,我仍然注意到我在特定网站上测试了一些超时。我无法100%确定这一点,但我认为这是因为支持http-server的网站的性质通过执行ip检查将用户请求限制为最大值因此,返回一些HTTP 400消息以防止可能的攻击&#39;在服务器上。
答案 0 :(得分:30)
修改:https://stackoverflow.com/a/37946324/744276的重复
默认情况下,Node有4 workers to resolve DNS queries。如果您的DNS查询需要很长时间,请求将阻止DNS阶段,症状正好是ESOCKETTIMEDOUT
或ETIMEDOUT
。
尝试增加你的uv线程池大小:
export UV_THREADPOOL_SIZE=128
node ...
或index.js
(或您的入口点):
#!/usr/bin/env node
process.env.UV_THREADPOOL_SIZE = 128;
function main() {
...
}
编辑:here is a blog post关于它。
答案 1 :(得分:2)
我发现如果有太多异步请求,那么在Linux中会发生ESOCKETTIMEDOUT异常。我发现的解决方法是这样做:
将此选项设置为request():
class SimpleThingWithMethods(object):
def __init__(self, n, x):
self._n = n
self._x = x
def x(self):
return self._x
def n(self):
return self._n
SimpleThingWithMethods(2,3).x()
请注意,在此之后,超时可能在说谎,因此您可能需要增加它。