在Node.js进程中运行setInterval导致内存泄漏

时间:2017-05-10 17:35:19

标签: javascript node.js memory-leaks

我遇到setInterval()导致Node.js应用程序内存泄漏的问题。该应用程序很简单:它每半小时唤醒一次,在MongoDB表中查看是否有任何工作要做(大部分时间没有),然后向符合条件的记录发送电子邮件。随着时间的推移(几天),内存从100MB增加到超过1GB。

我尝试将变量移到setInteveral之外来获取GC但没有运气。我错过了什么吗?

我正在使用New Relic监控交易,但在添加此工具之前,此问题仍然存在。

const transactionName = 'email-scheduler';
let invokeTransaction = newrelic.createBackgroundTransaction(transactionName,
    function () {
      sendEmail(function (error) {
        log.info("Job completed; ending transaction.");
        newrelic.endTransaction();
      });
    }); //must be outside of setInterval to be GC'd
if (RUN_SCHEDULER) {
  setInterval(invokeTransaction, JOB_INTERVAL_MINUTES * 1000 * 60);
}

function sendEmail(callback) {
  log.info('Scheduler woke up to send emails (set to send every ' + JOB_INTERVAL_MINUTES + ' minutes)');
  mongo.findUsersSince(180, function (err, result) {
    if (err) {
      log.error("Welcome emails could not be sent: " + err);
      callback(err);
    }
    else if (result && result instanceof Array) {
      api.sendEmail(resutlt);
    } else {
      callback(null);
    }
  });
}

当我使用像Cron这样的包而不是setInterval()时,这是替代版本。遭遇同样的问题:

function sendEmail(callback) {
  log.info('Scheduler woke up to send emails (set to send every ' + JOB_INTERVAL_MINUTES + ' minutes)');

  try {
    new CronJob('0 */' + JOB_INTERVAL_MINUTES + ' * * * *', function () {
      log.info('Scheduler woke up to send emails (set to send every ' + JOB_INTERVAL_MINUTES + ' minutes)');
      mongo.findUsersSince(OKTA_WAIT_MINUTES, function (err, result) {
        if (err) {
          log.error("Welcome emails could not be sent: " + err);
          callback(err);
        }
        else if (result && result instanceof Array) {
          api.sendEmail(resutlt);
        } else {
          callback(null);
        }
      });
    }, function () {
      log.info('Scheduler completed job.');
    }, RUN_SCHEDULER, "America/Los_Angeles");
  } catch (ex) {
    log.error("cron job pattern not valid");
  }
}

3 个答案:

答案 0 :(得分:1)

这听起来像是一个XY问题。我会说,首先使用setInterval在节点中创建调度程序并不是一个好主意。

相反,我会使用cronjob,这听起来更合适。

例如node-schedule之类的东西就像这样

const schedule = require('node-schedule')

schedule.scheduleJob('0 * * * *', function () {
  invokeTransaction()
})

这可能会解决您的内存泄漏问题。您也可以考虑泄漏来自您的方法,而不是setInterval或cron调度程序的实现。

答案 1 :(得分:1)

我认为进入全球范围的过程导致GC保持这些变量的存在。

我建议你考虑完全相反的方向,使用容器函数而不是全局变量。

我不知道New Relic的行为如何,但是使用这种设计的代码看起来像一个肮脏的例子:

const transactionName = 'email-scheduler';
function perform_transaction() {
  newrelic.createBackgroundTransaction(transactionName,
      function () {
        sendEmail(function (error) {
          log.info("Job completed; ending transaction.");
          newrelic.endTransaction();
        });
      });
}

function sendEmail(callback) {
  log.info('Scheduler woke up to send emails (set to send every ' + JOB_INTERVAL_MINUTES + ' minutes)');
  mongo.findUsersSince(180, function (err, result) {
    if (err) {
      log.error("Welcome emails could not be sent: " + err);
      callback(err);
    }
    else if (result && result instanceof Array) {
      api.sendEmail(resutlt);
    } else {
      callback(null);
    }
  });
}

if (RUN_SCHEDULER) {
  setInterval(perform_transaction, JOB_INTERVAL_MINUTES * 1000 * 60);
}

我不知道这是否有帮助,因为我不知道底层API的作用......但一般来说,在全局空间中保存变量会增加使该对象通过引用过时数据而占用内存的风险

修改

@occasl发现的这个特定案例的正确答案(请参阅此处的评论和@Aperçu的答案)是基础API缺少db.close()电话。

答案 2 :(得分:0)

使用setInterval不是一个好的软件架构实现。 您可以使用Node Cron

var CronJob = require('cron').CronJob;
var job = new CronJob('00 30 11 * * 1-5', function() {
  /*
   * Runs every weekday (Monday through Friday)
   * at 11:30:00 AM. It does not run on Saturday
   * or Sunday.
   */
  }, function () {
    /* This function is executed when the job stops */
  },
  true, /* Start the job right now */
  timeZone /* Time zone of this job. */
);