AWS上的node.js应用程序中的内存泄漏

时间:2016-03-22 21:52:42

标签: node.js memory-leaks

我在节点中有一些代码,基本上它是对外部服务进行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正在快速增加,所以它必须有泄漏。

1 个答案:

答案 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]