【问题标题】:Memory leak with setInterval running in a Node.js Process在 Node.js 进程中运行 setInterval 的内存泄漏
【发布时间】:2017-05-10 17:35:19
【问题描述】:

我遇到了 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");
  }
}

【问题讨论】:

  • 很好奇你为什么要这样做:let invokeTransaction = invokeTransaction = ... 这甚至不适合我。
  • 错字...已更正。
  • 为什么invokeTransaction 会被 GC 处理?它在全局空间中,永远不会被覆盖......我认为将整个逻辑放在容器函数中会更好,只使用局部变量 - 这将使 GC 更有可能清除它们(假设不长 - life 变量保持绑定范围)。

标签: javascript node.js memory-leaks


【解决方案1】:

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

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

node-schedule 之类的东西看起来像这样

const schedule = require('node-schedule')

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

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

【讨论】:

  • 有趣的是,这实际上是我开始的,但仍然是同样的问题。我将使用底层 MongoDB 调用进行更多测试。
  • 你是对的。这是底层代码,因为我忽略了将 db.close() 添加到 MongoDB 调用中。
  • 根据 node-schedule 文档:“如果你只想做类似“每 5 分钟运行一次这个函数”之类的事情,你会发现 setInterval 更容易使用,也更合适”跨度>
【解决方案2】:

我相信进入全局范围的移动导致 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 发现此特定情况的正确答案(参见此处的 cmets 和 @Aperçu 的答案)是底层 API 缺少 db.close() 调用。

【讨论】:

  • 我最初将代码放在导出块中,但要点很好。在我的 MongoDB 调用中添加 db.close() 之前,它仍然在泄漏。
【解决方案3】:

使用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. */
);

【讨论】:

  • 见下面的评论。同样的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-15
  • 2019-03-09
  • 1970-01-01
  • 1970-01-01
  • 2012-03-11
  • 2016-01-01
相关资源
最近更新 更多