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 模块的组合同时抓取大量链接。 我注意到很多 ETIMEDOUTESOCKETTIMEDOUT 错误,尽管链接是可访问的并且使用 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 方法,该方法使用maxAttempsretryDelay 参数来控制请求:

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 阶段阻塞,并且症状正好是 ESOCKETTIMEDOUTETIMEDOUT

尝试增加你的 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 错误

在 RDS 中访问 MySQL 或 MS SQL 时的 AWS Lambda node.js ETIMEDOUT

node.js createserver获取请求值

Node.js:get/post请求全局对象工具模块