我在节点中有一些代码,基本上它是对外部服务进行api调用并将返回的数据转储到数据库中。但它必须有一些严重的内存泄漏,因为节点服务器将在中间耗尽内存。我使用的AWS实例是2CPU,4GB RAM。我花了很多时间来弄清楚泄漏的地方还没有运气。下面是代码,任何提示都会有所帮助。
function refreshSitesBy5Min(rawData, callback){
var sites = JSON.parse(rawData).data;
if (typeof sites !== 'undefined' && sites.length > 0){
log.info('refreshing sites 5min');
sites.forEach(function(elem, index, array){
db.site_5min.find({siteid: elem.id, ts : moment(elem.ts).format('YYYY-MM-DDThh:mm:ss')}, function(err, found){
if (typeof found === 'undefined' || found == null || found.length == 0){
db.site_5min.save({
siteid : elem.id,
gran : '5min',
ts : moment(elem.ts).format('YYYY-MM-DDThh:mm:ss'),
wh_sum : elem.Wh_sum
}, function(err, inserted){
if (err){
log.error(err);
}
});
}
else{
db.site_5min.save({
id: found.id,
siteid : elem.id,
gran : '5min',
ts : moment(elem.ts).format('YYYY-MM-DDThh:mm:ss'),
wh_sum : elem.Wh_sum
}, function(err, updated){
if (err){
log.error(err);
}
})
}
})
})
}
else{
log.warn('no sites data');
}
callback();
}
这是调用上一个方法的代码:
function refreshSiteByGran(globalToken, gran, frequency){
log.info('refreshing site for ' + gran + ' table');
// db.site.find({}, function(err, sites){
db.run("select * from site", function(err, sites){
if (err){
log.error(err);
}
if (sites){
function handler(i){
if (i < sites.length){
var thePath = '/v3/sites/' + sites[i].siteid + '/data?fields=Wh_sum&tz=US/Pacific&gran=' + gran;
var end = moment().subtract(1, 'days').format('YYYY-MM-DDThh:mm:ss');
var start;
if (gran === '5min' || gran === 'hourly'){
start = moment(end).subtract(frequency, 'days').format('YYYY-MM-DDThh:mm:ss');
}
else if (gran === 'daily'){
start = moment(end).subtract(frequency, 'days').format('YYYY-MM-DDThh:mm:ss');
}
else if (gran === 'monthly'){
start = moment(end).subtract(frequency, 'months').format('YYYY-MM-DDThh:mm:ss');
}
thePath = thePath + '&start=' + start + '&end=' + end;
log.warn('thePath: ' + thePath);
var options = locusUtil.setOptions(thePath, globalToken.token.access_token);
request(options, function(err, result, body){
if (err){
log.error(err + ' path: ' + thePath);
}
if (body && JSON.parse(body).statusCode == 401){
getLocusToken(function(){
setTimeout(function(){
handler(i);
}, 2000);
})
}
else if (body && JSON.parse(body).statusCode == 200){
var data = JSON.parse(body).data;
// log.info('any data? ' + JSON.stringify(body, null, 4));
if (typeof data !== 'undefined' && data.length > 0){
if (gran === '5min'){
refreshSitesBy5Min(body, function(){
log.info('inserted: ' + data[0].id);
setTimeout(function(){
handler(i+1);
}, 2000);
})
}
if (gran === 'hourly'){
refreshSitesByHourly(body, function(){
log.info('inserted: ' + data[0].id);
setTimeout(function(){
handler(i+1);
}, 2000);
})
}
if (gran === 'daily'){
refreshSitesByDaily(body, function(){
log.info('inserted: ' + data[0].id);
setTimeout(function(){
handler(i+1);
}, 2000);
})
}
if (gran === 'monthly'){
refreshSitesByMonthly(body, function(){
log.info('inserted: ' + data[0].id);
setTimeout(function(){
handler(i+1);
}, 2000);
})
}
}
else{
setTimeout(function(){
handler(i+1);
}, 2000);
}
}
// re-try for concurrency error
else if (body && JSON.parse(body).statusCode == 429){
log.warn('error body ' + JSON.stringify(body));
setTimeout(function(){
handler(i);
}, 2000);
}
// if any other error, just skip
else {
setTimeout(function(){
handler(i+1);
}, 2000);
}
})
}
else{
return;
}
}
handler(0);
}
});
}
我相信问题出在这两个块里面,我用memwatch监视v8垃圾收集,我看到usage_trend正在快速增加,所以它必须有泄漏。
答案 0 :(得分:0)
这很容易解决......
首先,摆脱位于此处的forEach循环......
sites.forEach(function(elem, index, array){
相反,创建一个递归函数,只需将索引传递给下一个迭代。这样做是创建一个循环,根据给定的CPU和内存分配正确执行。不需要process.nextTick()
或任何那种花哨的爵士乐。
异步循环在技术上不是解决方案,因为它们会在数千个队列中快速超载系统。相反,遍历每个记录,然后仅在当前流程完成时继续执行下一个记录。
此外,在继续下一个之前删除当前数组索引。
最终,当索引返回&#34; undefined&#34;时,到达循环的结尾。这就是召唤主函数refreshSitesBy5Min
的回调。
function refreshSitesBy5Min(rawData, callback) {
var sites = JSON.parse(rawData).data
getSite(0)
function getSite(index) {
// we have reached the end
if(!sites[index])
return callback()
runProcess(sites[index]
// clear up memory after every iteration
delete sites[index]
// done with iteration, move on to the next
getSite(++index)
}
}
还没有完成......
大JSON对象 如果您的JSON对象很庞大,您将需要流式传输JSON并一次处理很小的块。 https://github.com/uhop/stream-json
大数据库结果集 如果一次返回500个以上的记录结果,则SQL查询应该使用限制,即使更小也更好。因此,如果您返回的记录集是100,000。只需在递归函数中一次获取500,只需递增索引并将其乘以num_records,在这种情况下:500。
var offset = iter * 500
limit: [offset, 500]