使用Node.js和mongodb处理超时问题

18

我目前正在测试一些代码对以下场景的适应性:

  • 启动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方法中使用的username变量实际上是在我的完整代码中声明的,我在此贴出的代码是一个简化版本,用于说明结构和错误检查。


2
这是一个好问题:db.collection(上面的第1点)在数据库连接丢失时不会返回错误。collection.find(上面的第2点)最终会返回一个错误,但需要超过1分钟的时间。我尝试了在连接字符串中设置connectionTimeoutMS和socketTimeoutMS参数,但行为没有改变。 - Jesus Ruiz
嘿@JesusRuiz,我让查找调用运行了大约15-20分钟,但它从未回调。我之前尝试更改超时属性,但正如你所提到的,它们似乎没有任何效果!我不确定我是否发现了驱动程序中的错误,还是有些东西我仍然没有看到:( - user1243584
4个回答

10

更新:
根据此帖子,看起来他们已经部署了修复程序,将执行与我们此处所做相同的操作。不确定npm是否已经包含了这个修复程序(15.10.13)。https://github.com/mongodb/node-mongodb-native/issues/1092#ref-commit-2667d13

经过一些调查,我已经理解了发生了什么:
每次调用任何与数据库相关的方法(如查找、更新、插入等),都会创建游标,该游标具有自己的ID,并向Db的EventEmitter注册以便稍后进行回调。同时,它将自己注册到同一CallBackStore中的_notReplied对象中。

但是一旦连接关闭,我找不到任何迭代_through_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);
  }
});

这会做什么?一旦连接关闭,它将遍历所有未回复的游标,并触发带有错误信息“连接已关闭!”的事件。

测试用例:

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 ,我们将看到他们对此的回答。


太好了!我下班后会试一试,然后告诉你结果。谢谢。 - user1243584
嘿,我尝试了一下,但我不确定它是否符合我的要求。只有在尝试时才意识到,如果例如数据库服务器在执行find()之前崩溃,则关闭事件将在此之前发生,使find()调用无法回调。我认为唯一的方法就是在执行find()之前检查连接状态,并希望在那几毫秒内一切正常! - user1243584
你正在使用MongoClient,它不是直接的DB类,因此我已经针对你的情况更新了我的回答。 - moka
1
抱歉,我遇到的问题是_callBackStore不存在,当我测试没有打开游标时它如何响应。假设当没有待处理的回调时,_callBackStore被明确设置为未定义? - user1243584
我已经编辑了答案以检查其是否存在。这很奇怪,因为当Db类被初始化时,它应该存在。 - moka
显示剩余9条评论

1
我遇到了同样的问题,从谷歌搜索中找到了这个页面。 但是你选择的答案没有解决问题,和你一样,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;

首先启动服务器并初始化(init())它。
然后您可以要求它并使用它。

mongo.getdb(function(err, db) {
  if (err) {
    console.log(err);
  } else {
    db.collection('user').find({'xxx':'xxx'}).toArray(function(err, items) {
      console.log(items);
    });
  }
});


0

我正在使用Hapi和Mongodb(不使用mongoose)创建API。功能包括:

  1. 只有Mongodb可用时,开始响应API请求
  2. 如果在处理过程中Mongodb出现问题,停止响应
  3. 当Mongodb再次可用时重新启动
  4. 为所有请求保持单一连接

结合其他答案和这篇文章的一些思路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});

    }
};

我正在将数据库连接导出到全局空间。感觉这不是最好的解决方案,但我曾经有过需要将数据库连接作为参数传递给所有模块的项目,那样更糟糕。也许应该采用一些模块化的方法,在需要时导入数据库连接,但在我的情况下,我几乎在任何地方都需要它,我必须在大多数文件中编写包含语句。没有与数据库的连接,这个API就毫无意义,所以我认为这可能是最好的解决方案,即使我反对在全局空间中使用某些神奇的东西。


0
经过进一步的调查,似乎无法指定“离线”超时,例如上述场景。唯一可以指定的超时是在不活动10分钟后通知服务器超时游标,但是像上述场景中一样,与服务器的连接断开这种方法并不起作用。
供参考,我在这里找到了信息: https://github.com/mongodb/node-mongodb-native/issues/987#issuecomment-18915263,我相信这是该项目的主要贡献者之一。

链接失效了,可能他们改变了位置。 - Dmitri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接