我正在使用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
答案 0 :(得分:13)
默认情况下,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 :(得分: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 });
详细答案;
在循环内执行请求时,我遇到了完全相同的问题。但是,上述方法对我没有任何帮助。
为了在一段时间后停止在某些请求上获得ETIMEDOUT
和ESOCKETTIMEDOUT
,请执行以下操作:
request
设置。相反,仅在循环之前一次用您的设置创建一个request
包装器。正如request documentation所述:请注意,如果您要循环发送多个请求并创建 多个新的池对象,maxSockets将无法正常工作。至 要解决此问题,请在您的池选项中使用request.defaults 或使用maxSockets属性创建池对象 循环。
{ 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)