使用 Node.js 和 mongodb 处理超时
Posted
技术标签:
【中文标题】使用 Node.js 和 mongodb 处理超时【英文标题】:Handling timeouts with Node.js and mongodb 【发布时间】:2013-09-12 08:30:46 【问题描述】:我目前正在测试一些代码如何应对以下场景:
Node.js 应用程序已启动并成功建立与 mongodb 的连接 成功建立连接后,mongodb服务器死掉,所有后续请求都失败为此,我得到了以下使用官方驱动程序的代码(在此处找到:https://github.com/mongodb/node-mongodb-native):
MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db)
app.get('/test', function(req, res)
db.collection('users', function (err, collection)
console.log(err);
if (err)
// ## POINT 1 ##
// Handle the error
else
collection.find( 'username': username , timeout: true ).toArray(function(err, items)
console.log(err);
if (err)
// ## POINT 2 ##
// Handle the error
else
if (items.length > 0)
// Do some stuff with the document that was found
else
// Handle not finding the document
);
);
);
);
由于在处理请求时 mongodb 服务器不再运行,我假设在我标记为 ## POINT 1 ## 或 ## POINT 2 ## 的点上,它会返回指示超时的错误;然而,事实并非如此。
我尝试了许多不同的设置(包括您可以在此处看到的明确允许光标超时的设置),但是我似乎无法以任何方式启用它。在我尝试过的每个配置中,Node.js 只会继续等待 find() 操作回调,但它永远不会。
如果我在运行 mongodb 之前启动 Node.js 应用程序,它会很好地捕获连接回调中的错误,但如果在此之后连接断开,它似乎不会以任何方式处理它.
是否有我遗漏的设置,或者在建立连接后无法检测到连接被终止?
编辑:为了清楚起见,find 方法中使用的用户名变量实际上是在我的完整代码中声明的,我在这篇文章中放置的代码是一个简化版本,用于说明结构和错误检查.
【问题讨论】:
这是一个很好的问题:当 db 连接丢失时,db.collection(上面的第 1 点)不会返回错误。 collection.find(上面的第 2 点)最终会返回一个错误,但它会在超过 1 分钟后返回。我在连接字符串中使用了 connectionTimeoutMS 和 socketTimeoutMS 设置,但行为没有改变。 嘿 @JesusRuiz,我认为 find 呼叫运行了大约 15-20 分钟,但它从未回拨。我之前曾尝试更改超时属性,但正如您所提到的,它们似乎没有任何效果!我不确定我是否在驱动程序中发现了一个错误,或者是否还有一些我仍然没有看到的东西:( 【参考方案1】:经过进一步调查,您似乎无法指定“离线”超时,例如上述情况。唯一可以指定的超时是通知服务器在 10 分钟不活动后使光标超时,但是在上面的场景中,与服务器的连接断开,这不起作用。
作为参考,我在这里找到了信息:https://github.com/mongodb/node-mongodb-native/issues/987#issuecomment-18915263,我认为他是该项目的主要贡献者之一。
【讨论】:
链接不好,可能是他们换了位置。【参考方案2】:UPD: 根据这篇文章,看起来他们已经部署了与我们在这里所做的相同的修复。不确定这是否已经在 npm (15.10.13) 内。 https://github.com/mongodb/node-mongodb-native/issues/1092#ref-commit-2667d13
经过一番调查,我设法了解那里发生了什么: 每次您调用任何方法来处理数据库(查找、更新、插入等)时,它都会创建具有自己 ID 的游标,并将自身注册到 Db 的 EventEmitter 以便稍后回调。同时,它将自身注册到同一 CallBackStore 中的 _notReplied 对象。
但是一旦连接关闭,我就找不到任何会遍历 _notReplied 游标并会因错误或任何带有计时器的逻辑而触发它们的东西(它仍然可能在某处)。所以我设法编写了一些小工作,当 DB 发出 close
事件时,它确实会强制触发游标出错:
new mongodb.Db('testdb', new mongodb.Server('localhost', 27017, ), safe: true ).open(function (err, db)
if (!err)
db.on('close', function()
if (this._callBackStore)
for(var key in this._callBackStore._notReplied)
this._callHandler(key, null, 'Connection Closed!');
);
// ...
else
console.log(err)
);
我建议使用第一种方法而不是 MongoClient。原因很少:例如,当您关闭连接然后调用.find
时,它会正确触发回调中的错误,而使用 MongoClient 则不会。
如果您使用的是 MongoClient:
MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db)
if (!err)
db.on('close', function()
if (this._callBackStore)
for(var key in this._callBackStore._notReplied)
this._callHandler(key, null, 'Connection Closed!');
);
// ...
else
console.log(err);
);
这会做什么?一旦连接关闭,它将遍历所有 _notReplied 游标并为它们触发事件,错误为 Connection Closed!
。
测试用例:
items.find( ).toArray(function(err, data)
if (!err)
console.log('Items found successfully');
else
console.log(err);
);
db.close();
这将强制关闭数据库连接并触发您之前处理的close
事件,并确保将关闭游标。
更新: 我在 GitHub 上添加了问题:https://github.com/mongodb/node-mongodb-native/issues/1092 我们会看看他们对此有何评论。
【讨论】:
很好的发现!当我下班回来时,我会试一试,然后告诉你结果如何。谢谢。 嘿,我试了一下,但我不确定它是否符合我的要求。我只是在尝试时意识到这将无法处理例如数据库服务器在执行 find() 之前崩溃的情况,因为关闭事件将提前发生,从而使 find() 调用卡住而没有回调。我认为唯一的方法是在执行 find() 之前检查连接状态,并希望在这几毫秒内一切正常! 顺便说一句,我实际上无法让您的 sn-p 工作,我在 db 被实例化并打开后放置它,但是其中没有 _callBackStore 对象,这是我能找到的最接近的对象这是 this.serverConfig._callBackStore._notReplied 您使用的是 MongoClient,它不是直接的 DB 类,所以我已经为您的案例更新了我的答案。 抱歉,我遇到的 _callBackStore 不存在的问题是在测试没有打开游标时它如何响应。大概 _callBackStore 在没有待处理的回调时显式设置为未定义?【参考方案3】:我有同样的问题,并从谷歌找到了这个页面。 但是你选择的答案并没有解决问题,和你一样,this._callBackStore 不能使用
但我尝试包装 Mongo,它似乎工作正常
var MongoClient = require('mongodb').MongoClient;
var mongo = ;
mongo.init = function()
MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db)
if (err)
mongo.DB = '';
else
mongo.DB = db;
db.on('close', function()
mongo.DB = '';
);
db.on('reconnect', function()
mongo.DB = db;
);
mongo.getdb = function(callback)
if (mongo.DB)
callback(null, mongo.DB);
else
callback('can not connect to db', null);
module.exports = mongo;
首先启动服务器并初始化()它
然后你可以要求它并使用它
mongo.getdb(function(err, db)
if (err)
console.log(err);
else
db.collection('user').find('xxx':'xxx').toArray(function(err, items)
console.log(items);
);
);
【讨论】:
【参考方案4】:我正在使用 Hapi 和 Mongodb(不带猫鼬)制作 api。特点:
-
仅当 mongo db 可用时才开始响应 API 请求
如果 mongo 在循环期间死亡,则停止响应
当 mongo 再次可用时重新启动
为所有请求保持单一连接
结合其他答案的一些想法和这篇文章https://productbuilder.wordpress.com/2013/09/06/using-a-single-global-db-connection-in-node-js/我的方法是这样的:
server.js
Utilities.initializeDb(() =>
server.start((err) =>
if (err) throw err;
console.log('Server running at:', server.info.uri);
);
, () =>
server.stop((err) =>
if (err) throw err;
console.log('Server stopped');
);
);
Utilities.js
"use strict";
const MongoClient = require('mongodb').MongoClient;
const MongoUrl = 'mongodb://localhost:27017/db';
export const Utilities =
initializeDb: (next, onCrash) =>
const ConnectToDatabase = (params) =>
MongoClient.connect(MongoUrl, (err, db) =>
if (err !== null)
console.log('#t4y4542te Can not connect to mongo db service. Retry in 2 seconds. Try #' + params.retry);
console.error(err);
setTimeout(() =>
ConnectToDatabase(retry: params.retry + 1);
, 2000);
else
db.on('close', () =>
onCrash();
console.log('#21df24sf db crashed!');
ConnectToDatabase(retry: 0);
);
global.db = global.db || db;
next();
);
;
ConnectToDatabase(retry: 0);
;
我正在将数据库连接导出到全局空间。这感觉不是最好的解决方案,但我有一些项目,其中 db 连接作为参数传递给所有模块,而且更糟糕。也许应该有一些模块化方法,您可以在需要的地方导入数据库连接,但在我的情况下,我几乎在任何地方都需要它,我必须在大多数文件中编写包含语句。这个 API 没有与 db 的连接是毫无意义的,所以我认为它可能是最好的解决方案,即使我反对让某些东西在全球空间中神奇地飞行..
【讨论】:
以上是关于使用 Node.js 和 mongodb 处理超时的主要内容,如果未能解决你的问题,请参考以下文章
Node.js 和 MongoDB:保存集合还是每次都抓取它们?
Node.js 和 Telegraf TimeoutError: Promise 在 90000 毫秒后超时