Node.js 请求模块获取 ETIMEDOUT 和 ESOCKETTIMEDOUT
Posted
技术标签:
【中文标题】Node.js 请求模块获取 ETIMEDOUT 和 ESOCKETTIMEDOUT【英文标题】:Node.js request module getting ETIMEDOUT and ESOCKETTIMEDOUT 【发布时间】: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 GET Request ETIMEDOUT & ESOCKETTIMEDOUT的可能重复 【参考方案1】:编辑:https://***.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()
...
编辑 1:I also wrote a blog post 关于它。
编辑 2:如果查询不唯一,您可能需要使用缓存,例如 nscd。
【讨论】:
感谢您对本主题的有益补充,我尚未测试您的建议,但知道这一点仍然很好。一旦我有空闲时间,我也会报告结果:) 我读到的关于 UV_THREADPOOL_SIZE 的内容表明,这对于阻塞 io(例如磁盘访问)最重要,但对于非阻塞 io(例如网络访问)则无关紧要。 没错,只是由于getaddrinfo(3)
的工作方式,DNS 解析也会被阻止。【参考方案2】:
我发现如果异步请求太多,那么Linux中会发生ESOCKETTIMEDOUT异常。我发现的解决方法是这样做:
将此选项设置为 request():
agent: false, pool: maxSockets: 100
请注意,在那之后,超时可能会撒谎,因此您可能需要增加它。
【讨论】:
首先,这对我有用,因为我下载了几百个导致这个错误的小文件。其次,增加socket数量有什么坏处?它们会在某个时候自动关闭吗?以上是关于Node.js 请求模块获取 ETIMEDOUT 和 ESOCKETTIMEDOUT的主要内容,如果未能解决你的问题,请参考以下文章
Node.Js AWS SES - 发送电子邮件时的 ETIMEDOUT
在 node.js 中使用 node-fetch 将数据发送到 Pure Data (Pd) 导致 ETIMEDOUT
从 Node.js 连接到 SQL 数据库 (AWS RDS) 而没有 ETIMEDOUT 错误