Node.js GET请求ETIMEDOUT& ESOCKETTIMEDOUT

时间:2014-06-20 05:34:53

标签: javascript node.js node-request

我正在使用Node.js - async&请求模块抓取超过1亿个网站,我不断遇到错误ESOCKETTIMEDOUT&几分钟后ETIMEDOUT

重新启动脚本后它再次起作用。它似乎不是连接限制问题,因为我仍然可以毫不拖延地执行resolve4,resolveNs,resolveMx和curl

你看到代码有什么问题吗?或任何建议?我想将async.queue()并发推高到至少1000.谢谢。

var request = require('request'),
    async = require('async'),
    mysql = require('mysql'),
    dns = require('dns'),
    url = require('url'),
    cheerio = require('cheerio'),
    iconv = require('iconv-lite'),
    charset = require('charset'),
    config = require('./spy.config'),
    pool = mysql.createPool(config.db);

iconv.skipDecodeWarning = true;

var queue = async.queue(function (task, cb) {
    dns.resolve4('www.' + task.domain, function (err, addresses) {
        if (err) {
            //
            // Do something
            //
            setImmediate(function () {
                cb()
            });
        } else {
            request({
                url: 'http://www.' + task.domain,
                method: 'GET',
                encoding:       'binary',
                followRedirect: true,
                pool:           false,
                pool:           { maxSockets: 1000 },
                timeout:        15000 // 15 sec
            }, function (error, response, body) {

                //console.info(task);

                if (!error) {
                  // If ok, do something

                } else {
                    // If not ok, do these

                    console.log(error);

                    // It keeps erroring here after few minutes, resolve4, resolveNs, resolveMx still work here.

                    // { [Error: ETIMEDOUT] code: 'ETIMEDOUT' }
                    // { [Error: ESOCKETTIMEDOUT] code: 'ESOCKETTIMEDOUT' }

                    var ns = [],
                        ip = [],
                        mx = [];
                    async.parallel([
                        function (callback) {
                            // Resolves the domain's name server records
                            dns.resolveNs(task.domain, function (err, addresses) {
                                if (!err) {
                                    ns = addresses;
                                }
                                callback();
                            });
                        }, function (callback) {
                            // Resolves the domain's IPV4 addresses
                            dns.resolve4(task.domain, function (err, addresses) {
                                if (!err) {
                                    ip = addresses;
                                }
                                callback();
                            });
                        }, function (callback) {
                            // Resolves the domain's MX records
                            dns.resolveMx(task.domain, function (err, addresses) {
                                if (!err) {
                                    addresses.forEach(function (a) {
                                        mx.push(a.exchange);
                                    });
                                }
                                callback();
                            });
                        }
                    ], function (err) {
                        if (err) return next(err);

                        // do something
                    });

                }
                setImmediate(function () {
                    cb()
                });
            });
        }
    });
}, 200);

// When the queue is emptied we want to check if we're done
queue.drain = function () {
    setImmediate(function () {
        checkDone()
    });
};
function consoleLog(msg) {
    //console.info(msg);
}
function checkDone() {
    if (queue.length() == 0) {
        setImmediate(function () {
            crawlQueue()
        });
    } else {
        console.log("checkDone() not zero");
    }
}

function query(sql) {
    pool.getConnection(function (err, connection) {
        if (!err) {
            //console.log(sql);
            connection.query(sql, function (err, results) {
                connection.release();
            });
        }
    });
}

function crawlQueue() {
    pool.getConnection(function (err, connection) {
        if (!err) {
            var sql = "SELECT * FROM domain last_update < (UNIX_TIMESTAMP() - 2592000) LIMIT 500";
            connection.query(sql, function (err, results) {
                if (!err) {
                    if (results.length) {
                        for (var i = 0, len = results.length; i < len; ++i) {
                            queue.push({"id": results[i]['id'], "domain": results[i]['domain'] });
                        }
                    } else {
                        process.exit();
                    }
                    connection.release();
                } else {
                    connection.release();
                    setImmediate(function () {
                        crawlQueue()
                    });
                }
            });
        } else {
            setImmediate(function () {
                crawlQueue()
            });
        }
    });
}
setImmediate(function () {
    crawlQueue()
});

系统限制非常高。

    Limit                     Soft Limit           Hard Limit           Units
    Max cpu time              unlimited            unlimited            seconds
    Max file size             unlimited            unlimited            bytes
    Max data size             unlimited            unlimited            bytes
    Max stack size            8388608              unlimited            bytes
    Max core file size        0                    unlimited            bytes
    Max resident set          unlimited            unlimited            bytes
    Max processes             257645               257645               processes
    Max open files            500000               500000               files
    Max locked memory         65536                65536                bytes
    Max address space         unlimited            unlimited            bytes
    Max file locks            unlimited            unlimited            locks
    Max pending signals       257645               257645               signals
    Max msgqueue size         819200               819200               bytes
    Max nice priority         0                    0
    Max realtime priority     0                    0
    Max realtime timeout      unlimited            unlimited            us

的sysctl

net.ipv4.ip_local_port_range = 10000    61000

4 个答案:

答案 0 :(得分:13)

默认情况下,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() {
   ...
}

编辑:here is a blog post关于它。

答案 1 :(得分:4)

我有同样的问题。它通过使用&#34; agent:false&#34;来解决。在阅读this discussion后的请求选项中。

2017年10月31日 上面的原始响应似乎并没有完全解决问题。我们发现的最终解决方案是在代理中使用keepAlive选项。例如:

var pool = new https.Agent({ keepAlive: true });

function getJsonOptions(_url) {
    return {
        url: _url,
        method: 'GET',
        agent: pool,
        json: true
    };
}

节点的默认池似乎默认为keepAlive = false,这会导致在每个请求上创建新连接。如果在短时间内创建了太多连接,则会出现上述错误。我的猜测是,沿着服务路径的一个或多个路由器阻止连接请求,可能是怀疑拒绝服务攻击。无论如何,上面的代码示例完全解决了我们的问题。

答案 2 :(得分:1)

TL:DR; 仅在循环之前一次用您的设置配置request的包装,并使用forever: true设置。

const customRequest = request.defaults({ forever: true }); // wrapper
customRequest({ uri: uri });

详细答案;
在循环内执行请求时,我遇到了完全相同的问题。但是,上述方法对我没有任何帮助。

为了在一段时间后停止在某些请求上获得ETIMEDOUTESOCKETTIMEDOUT,请执行以下操作:

  1. 请勿在循环中的每个请求上配置request设置。相反,仅在循环之前一次用您的设置创建一个request包装器。正如request documentation所述:

请注意,如果您要循环发送多个请求并创建 多个新的池对象,maxSockets将无法正常工作。至 要解决此问题,请在您的池选项中使用request.defaults 或使用maxSockets属性创建池对象 循环。

  1. 但是,即使执行该操作并使用{ maxSockets: Infinity }配置池,我仍然面临错误。 解决我的问题的唯一配置forever: true。这将使建立的连接保持活动状态。

因此,最后我的代码是这样的:

const request = require('request');
// Custom wrapper
const customRequest = request.defaults({
   forever: true,
   timeout: 20000,
   encoding: null
})

loop(urls, (url) => { // for each of my urls
   customRequest({uri: url}, (err, res, body) => { 
      console.log("done"); 
   });
});

使用该策略,我能够以每秒25个请求的速度处理大约40万个请求,而在此过程中没有任何问题(Ubuntu 18.04 VM,4GB RAM,默认值为UV_THREADPOOL_SIZE)。

答案 3 :(得分:-1)

在请求工具(https://github.com/request/request

http连接保持活动默认情况下是关闭的。

您需要设置option.forever = true才能打开此功能。