【发布时间】:2019-06-04 17:58:29
【问题描述】:
我的情况是,我在谷歌应用引擎(使用 flex 环境)上有一个 CRON 任务,该任务在 一段时间 时间后就死掉了,但我没有任何痕迹为什么(检查了 GA 日志,什么也没有,尝试尝试/捕获,并明确记录它 - 没有错误)。
我已经明确验证,如果我创建一个运行 8 分钟的 cron 任务(但没有做太多 - 只是每秒休眠并更新数据库),它将成功运行。 这只是为了证明 CRON 作业至少可以运行 8 分钟。 & 我已经正确设置了 Express 和 NodeJS 组合。
这一切都很好,但似乎我的 other cron 作业会在 2-3 分钟内死掉,非常快。它正在达到某种极限,但我不知道如何控制它,甚至不知道它是什么极限,所以我只能推测。
我将详细介绍我的 CRON 任务。它基本上是快速查询 MongoDB 数据库,其中每个查询都非常快。我在本地试过同样的代码,没有问题。
我的猜测是我以某种方式一次创建了太多 MongoDB 请求,并且可能会用完一些东西?
这是一个伪代码(只是为了描述我们在谈论什么样的规模数据——数字和流量完全一样):
function q1() {
return await mongoExecute(async (db) => {
const [l1, l2] = await Promise.all([
db.collection('Obj1').count({uid1: c1, u2action: 'L'}),
db.collection('Obj1').count({uid2: c2, u1action: 'L'}),
]);
return l1+l2;
});
}
for(let i = 0; i < 8000; i++) {
const allImportantInformation = Promise.all([
q1(),
q2(),
q3(),
.....
q10()
])
await mongoDb.saveToServer(document);
}
在 CRON 作业在没有任何解释的情况下死掉之前,它已经在 i=1600 附近。 GA Cron Job 面板清楚地显示 JOB 失败。
这也是我的 mongoExecute(它只是一个单独的模块,用于缓存 db 对象,希望这是确保 mongodb 池正常工作的正确做法。)
import { MongoClient, Db } from 'mongodb';
let db = null;
let promiseInProgress = null;
export async function mongoExecute<T> (executor: (instance: Db) => T): Promise<T | null> {
if (!db) {
if (!promiseInProgress) {
promiseInProgress = new Promise(async (resolve, reject) => {
const tempDb = await MongoClient.connect(process.env.MONGODB_URL);
resolve(tempDb);
});
}
db = await promiseInProgress;
}
try {
const value = await executor(db);
return value;
} catch (error) {
console.log(error);
return null;
}
}
解决办法是什么?我的想法是基本上确保一次发出的请求更少(所以所有的承诺都是顺序的,并可能在 FOR 中的每个周期之间增加睡眠。
我不明白,因为它在某个特定点之前都可以正常工作(而且相当大的点,它肯定是不同的数量,有时是 800,有时是 1200,等等)。
是否有任何“TCP 连接用完”的情况发生?从理论上讲,我们不应该用完任何东西,因为我们在任何时候都没有太多的开放空间。
如果我在每个周期之间等待 200 毫秒似乎可以工作 & 我怀疑我可以找到解决方案,所有项目都不必在同一个 CRON 执行中更新,但它有点烦人,我想知道是怎么回事。
垃圾收集器是否赶得上速度不够快,为什么 GA 会默默地让我的 cron 任务失败?
【问题讨论】:
-
我的猜测是
MongoClient.connect()代码被快速调用,因此该作业占用了太多 TCP 连接,因为节点驱动程序中的connect()创建了一个包含 5 个连接的连接池默认。这可以解释为什么要等待 200 毫秒才能使其工作,因为代码发现db不再为空,因此它不会快速触发connect()方法并正确使用现有的连接池。基本上不用等待,代码很快就创建了很多连接池。随着等待,它只创建一个连接池。 -
您可以通过将
MongoClient.connect()代码移出MongoExecute到代码的主要部分来检查是否确实如此。将db对象传入每个q()函数。无论如何,这是最佳实践,MongoClient.connect()应该在每次应用程序执行时只调用一次,因为它会创建一个连接池而不是与数据库的单个连接。 -
这是一个有趣的理论,尽管据我了解 CRON 作业中的执行是单线程的(因为 JS 是单线程的),这意味着没有竞争条件(MongoClient.connect 应该是只调用一次)。但是,实际测试它并没有什么坏处,所以我会试一试。谢谢
-
@KevinAdistambha 我试过了,但它就像预期的那样:mongoExecute() 只会在应用程序生命周期内创建单个连接,无论 mongoExecute() 执行的速度有多快。我还通过更新 mongodb 驱动程序解决了这个问题。
标签: node.js mongodb express google-app-engine cron