【问题标题】:Nodejs function running every 2 minutes lost over time每 2 分钟运行一次的 Nodejs 函数会随着时间的推移而丢失
【发布时间】:2019-11-17 23:31:19
【问题描述】:

这是一个很难描述的问题。 我有一个koajs 应用程序,该应用程序具有每 2 分钟在多个实例(10-1000 范围)中创建的功能。此计划作业在应用程序启动时创建。我使用koajs,因为我需要一些简单的 api 端点用于这个应用程序。前 3-5 小时运行良好,然后创建的实例数开始减少,部分日志输出消失。

这是基于实际代码的最小示例:

server.ts

const bootstrap = async () => {
    process.setMaxListeners(0); //(node:7310) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit
                                //appears on app startup (however seems like this setMaxListeners(0) doesnt affect anything since the warning persist)
    const app = new Koa();
    app.use(async ctx => {
        ctx.body = "Welcome to my Server!";
    });
    app.listen(port);
    new Main().run();
};

bootstrap(); 

ma​​in.ts(尝试:cron npm 包,node-schedulersetInterval,递归setTimeout)运行scheduledJobWrapper

    isStarting: boolean = false;

    async run() {
        logger.info(`running the app, every 2 minutes`);

        //let that = this;
        // new CronJob(`*/2 * * * *`, function () {
        //     that.scheduledJobWrapper();
        // }, null, true, 'America/Los_Angeles');

        const interval = 2 * 60 * 1000;
        setInterval(() => {
            this.scheduledJobWrapper();
        }, interval);
    }

    async scheduledJobWrapper() {
        logger.info("here scheduledJobWrapper");

        let args = {};
        //some irrelevant logic to set the arguments
        await this.scheduledJob(args);
    }

    async scheduledJob(args) {
        try {
            logger.info("starting");
            if (!this.isStarting) {
                this.isStarting = true;

                const runningCount = Executor.tasks.length; //Executor.tasks is a singleton containing some info about tasks. details are irrelevant. the point is it contains the active tasks.
                const tasksLimit = 100;

                if (runningCount < tasksLimit) {
                    for await (const i of Array(tasksLimit - runningCount).keys()) {
                        if (Executor.tasks.length > 20)
                            await global.sleep(5 * 1000);

                        this.startWrapper(args); //calling main task here
                    }
                }
                this.isStarting = false;

                logger.info(`Started: ${Executor.tasks.length - runningCount}`);
            }
        } catch (e) {
            logger.error("Error running scheduled job: " + e.toString());
        }
    }

在本例中,问题表现如下: 前 3-5 小时所有工作都按预期工作,之后每次调用计划的函数:

  1. logger.info("here scheduledJobWrapper"); 现在会显示任何输出。
  2. logger.info("starting"); 不在输出中
  3. this.startWrapper 确实运行并且其中的代码正在执行。

尽管this.startWrapper 内部的代码仍在运行,但新创建的作业数量正在缓慢减少。 硬件 (RAM/CPU) 没有得到任何显着负载(CPU 低于 10%,RAM 低于 20%)

关于可能原因的任何线索?

nodejs: 12.6.0

谢谢!

更新 似乎使用setInterval 后,应用程序可以正常运行更长的时间(6-24 小时),但之后问题仍然存在。

【问题讨论】:

  • 可能不是问题,但我建议不要使用 arguments 作为变量名,因为标识符是在所有 JS 函数中预定义的。 more info
  • 不是实际的变量名,只是举例
  • 我建议在await global.sleep(5 * 1000); 之前加上logger.info 声明。用它来打印Executor.tasks.length的值。
  • 我认为这是一个泄漏的异步/等待逻辑问题,而不是直接 nodejs 限制。不幸的是,它的代码被破坏了,很难说。一个工作示例会更好,因为 async/await 存在问题。 1) 为什么使用for await 来迭代一个简单的数组? 2) 您读取了三个可能不同的Executor.tasks.length 值,具体取决于任务数组何时相对于检查被修改,所以 3) 为什么假设 Executor.tasks 管理不是问题的一部分?您为 20 多个任务添加 5s 延迟,最大值为 100,这保证批次超过 2m setInterval 间隔,使其可重入。
  • for await 不是这样工作的。

标签: javascript node.js typescript


【解决方案1】:

问题在于setInterval 函数。它会随着时间变慢。它也有奇怪的行为。您可以使用setTimeout 创建自定义 setInterval 或使用第三方模块并尝试。

示例 setInterval 实现。

const intervals = new Map();
function setInterval(fn, time, context, ...args) {
  const id = new Date().getTime() + "" + Math.floor(Math.random() * 10000);
  intervals.set(
    id,
    setTimeout(function next() {
      intervals.set(id, setTimeout(next, time));
      fn.apply(context, args);
    }, time)
  );
  return id;
}
function clearInterval(id) {
  clearTimeout(intervals.get(id));
}

setInterval(console.log, 100, console, "hi");

您还可以通过在下一个 setTimeout 中添加增量时间损失来增强。 这意味着如果时间丢失,请提前运行下一个 setTimeout。

【讨论】:

  • 我目前正在做类似的事情 - 拥有 setInterval 实现。问题仍然存在。唯一的解决方法仍然是使用pm2 --max-memory-restart - 它会在大约 12 小时内重新启动一次。
  • 如果内存问题不是解决方案。您没有突出显示内存问题!
【解决方案2】:

首先,最好将 Main() 的实例移动到监听范围内:

app.listen(port, () => {
   new Main().run();
});

我不知道在后端运行 setInterval 函数有多好。最好把这个逻辑提取出来放到 cron 作业中。

我们确定这台机器可以运行 100 个任务吗?请按顺序计算任务,看看问题何时开始。可能你不能安排 100 个任务并且在某处存在一个限制

【讨论】:

  • 机器甚至可以运行 1000 个这样的任务。一切运行良好,但内存泄漏存在。是的,不幸的是无法在 cron 作业中运行它。
  • 请@user1935987 意识到这段代码不能再运行了。您必须设置一个 cron 作业以每 2 分钟调用一个函数。在某个时间点安排更多任务后,您的应用程序会因此而崩溃。
  • 它当然可以运行并且正在运行,但是某处存在内存泄漏。现在只需在达到内存阈值后通过pm2 重新启动应用程序。涉及太多复杂的逻辑 - 必须重写很多才能通过 cron 运行它,而不是在这种特殊情况下的解决方案。找到内存泄漏是解决办法。
  • 内存泄漏是否来自服务器中运行的setInterval函数?可能会以某种方式克服 cron 作业的限制吧?
猜你喜欢
  • 2011-08-17
  • 1970-01-01
  • 2021-12-24
  • 2014-02-04
  • 1970-01-01
  • 2012-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多