【问题标题】:memory leak in node.js app on AWSAWS 上 node.js 应用程序中的内存泄漏
【发布时间】:2016-03-22 21:52:41
【问题描述】:

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

【问题讨论】:

  • 进程是否真的在某个时候崩溃?您是否尝试通过在命令行中添加--expose-gc 并每隔一段时间调用gc(); 来强制GC?
  • 是的,它确实崩溃了,并给我错误:致命错误:提交半空间失败。分配失败 - 进程内存不足。既然是内存泄漏,那么 force gc() 应该帮不上什么忙,我想找出哪里是泄漏并删除它
  • 数据库代码在哪里?可以发一下吗?
  • db 我正在使用一个名为massage的节点模块,将数据保存到db的实际代码是第一个块

标签: node.js memory-leaks


【解决方案1】:

这很容易解决...

首先,摆脱位于此处的 forEach 循环... sites.forEach(function(elem, index, array){

相反,创建一个简单地将索引传递给下一个迭代的递归函数。这样做是创建一个循环,该循环根据给定的 CPU 和分配的内存正确执行。不需要process.nextTick() 或任何花哨的爵士乐。

从技术上讲,异步循环并不是解决方案,因为它们会很快使系统超载数千个队列。相反,遍历每条记录,然后仅在当前进程完成时继续下一条。

另外,在继续下一个之前删除当前数组索引。 最终,当索引返回“未定义”时,循环结束。即调用主函数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]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-11
    • 2016-01-01
    • 2016-03-28
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多