何时在Node.js中关闭MongoDB数据库连接


76

通过Node MongoDB本机驱动程序使用Nodejs和MongoDB。需要检索一些文档,并进行修改,然后将其保存回来。这是一个例子:

db.open(function (err, db) {
  db.collection('foo', function (err, collection) {
    var cursor = collection.find({});
    cursor.each(function (err, doc) {
      if (doc != null) {
        doc.newkey = 'foo'; // Make some changes
        db.save(doc); // Update the document
      } else {
        db.close(); // Closing the connection
      }
    });
  });
});

具有异步性质,如果更新文档的过程花费更长的时间,则当光标到达文档末尾时,数据库连接将关闭。并非所有更新都保存到数据库中。

如果db.close()省略,则所有文档均正确更新,但应用程序挂起,永不退出。

我看到一则帖子建议使用计数器跟踪更新次数,当回落到零时,然后关闭数据库。但是我在这里做错了吗?处理这种情况的最佳方法是什么?是否db.close()必须使用它来释放资源?还是需要打开新的数据库连接?

Answers:


25

这是一种基于计数方法的潜在解决方案(我没有测试过,也没有错误陷阱,但应该可以传达出这种想法)。

基本策略是:获取需要更新的记录数,异步保存每条记录,并在成功时进行回调,这将减少计数并在计数达到0时关闭数据库(最后一次更新完成时)。通过使用{safe:true}我们可以确保每次更新均成功。

mongo服务器每个连接将使用一个线程,因此最好a)关闭未使用的连接,或b)池化/重用它们。

db.open(function (err, db) {
  db.collection('foo', function (err, collection) {
    var cursor = collection.find({});
    cursor.count(function(err,count)){
      var savesPending = count;

      if(count == 0){
        db.close();
        return;
      }

      var saveFinished = function(){
        savesPending--;
        if(savesPending == 0){
          db.close();
        }
      }

      cursor.each(function (err, doc) {
        if (doc != null) {
          doc.newkey = 'foo'; // Make some changes
          db.save(doc, {safe:true}, saveFinished);
        }
      });
    })
  });
});

5
@realguess,还有用于并发工具的库,可以帮助您完成这些工作,因此您无需管理细节。查看async.js,例如github.com/caolan/async
mpobrien 2011年

@mpobrien,您能否详细说明如何使用异步解决此问题?
2014年

您是否认为该解决方案在2017年仍然有效,或者您知道更好的方法吗?我正在考虑这样的事情,但是如果函数cursor.each(function (err, doc) {调用了异步函数,那么它将在回调中执行逻辑并在each()完成后可能需要数据库呢?如果在软件中进行了后续更改之后,该回调函数又调用了另一个异步函数(我希望您能理解),该怎么办?
水汪汪的

19

最好使用池化连接,然后在应用程序生命周期结束时在清理函数中调用db.close():

process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);

参见http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html

线程有点旧,但是无论如何。


这实际上给我带来了麻烦。有时,当我重新启动服务时,会收到Mongo“拓扑已破坏”错误,因为连接似乎断开了。难道我做错了什么?
ifightcrime,2016年

1
@ifightcrime:听起来很像正在运行的查询,但是您已经关闭了连接。取决于是否需要完成查询。如果您有写需要等待,我想您必须跟踪它们是手动完成的。您可以尝试在此处找到其工作原理:github.com/mongodb/node-mongodb-native/blob/2.1/lib/db.js#L366
pkopac

答案中的@pkopac链接-了解何时关闭Node.js中的MongoDB数据库连接(这是问到的问题)并没有帮助。注释中的pkopac链接已断开。尽管如此,我认为这是最好的答案,应将其标记为已接受的答案……
AS

6

我发现使用计数器可能适用于简单的场景,但是在复杂的情况下可能很难。这是我通过在数据库连接空闲时关闭数据库连接来提出的解决方案:

var dbQueryCounter = 0;
var maxDbIdleTime = 5000; //maximum db idle time

var closeIdleDb = function(connection){
  var previousCounter = 0;
  var checker = setInterval(function(){
    if (previousCounter == dbQueryCounter && dbQueryCounter != 0) {
        connection.close();
        clearInterval(closeIdleDb);
    } else {
        previousCounter = dbQueryCounter;
    }
  }, maxDbIdleTime);
};

MongoClient.connect("mongodb://127.0.0.1:27017/testdb", function(err, connection)(
  if (err) throw err;
  connection.collection("mycollection").find({'a':{'$gt':1}}).toArray(function(err, docs) {
    dbQueryCounter ++;
  });   
  //do any db query, and increase the dbQueryCounter
  closeIdleDb(connection);
));

这可以是任何数据库连接的通用解决方案。可以将maxDbIdleTime设置为与数据库查询超时相同的值或更长。

这不是很优雅,但是我想不出更好的方法来做到这一点。我使用NodeJs运行一个查询MongoDb和Mysql的脚本,如果数据库连接未正确关闭,该脚本将永远挂在那里。


1
嘿,感谢您的回答,但是您需要将clearInterval从closeIdleDb更改为checker :)。这真的帮助了我
RNikoopour

挺有意思!
水汪汪的

简单快速的解决方案。谢谢
Pedram marandi

2

这是我想出的解决方案。它避免了使用toArray,而且它很简短而且很可爱:

var MongoClient = require('mongodb').MongoClient;

MongoClient.connect("mongodb://localhost:27017/mydb", function(err, db) {
  let myCollection = db.collection('myCollection');
  let query = {}; // fill in your query here
  let i = 0;
  myCollection.count(query, (err, count) => { 
    myCollection.find(query).forEach((doc) => {
      // do stuff here
      if (++i == count) db.close();
    });
  });
});

如果// do stuff here部分最终写入数据库的其他异步调用该怎么办?他们不会发现它关闭吗?
水汪汪的

1

根据上面@mpobrien的建议,我发现异步模块在这方面非常有用。这是我要采用的示例模式:

const assert = require('assert');
const async = require('async');
const MongoClient = require('mongodb').MongoClient;

var mongodb;

async.series(
    [
        // Establish Covalent Analytics MongoDB connection
        (callback) => {
            MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
                assert.equal(err, null);
                mongodb = db;
                callback(null);
            });
        },
        // Insert some documents
        (callback) => {
            mongodb.collection('sandbox').insertMany(
                [{a : 1}, {a : 2}, {a : 3}],
                (err) => {
                    assert.equal(err, null);
                    callback(null);
                }
            )
        },
        // Find some documents
        (callback) => {
            mongodb.collection('sandbox').find({}).toArray(function(err, docs) {
                assert.equal(err, null);
                console.dir(docs);
                callback(null);
            });
        }
    ],
    () => {
        mongodb.close();
    }
);

您能否添加一些有关此工作原理和解决问题的解释?对于像我这样不懂异步的人。
水汪汪的

@ watery,async.series提供了一种在系列中调用异步函数的方法,在该函数中,直到前一个函数成功完成后,才调用后一个函数。在成功完成数组/对象中的所有功能之后,它最后提供了一个可选的回调,在这种情况下,我将使用该回调来最终关闭数据库连接。
安德鲁·柯克

0

我想出了一个涉及此类计数器的解决方案。它不依赖于count()调用,也不等待超时。每个()中的所有文档用完后,它将关闭数据库。

var mydb = {}; // initialize the helper object.

mydb.cnt = {}; // init counter to permit multiple db objects.

mydb.open = function(db) // call open to inc the counter.
{
  if( !mydb.cnt[db.tag] ) mydb.cnt[db.tag] = 1;
  else mydb.cnt[db.tag]++;
}; 

mydb.close = function(db) // close the db when the cnt reaches 0.
{
  mydb.cnt[db.tag]--;
  if ( mydb.cnt[db.tag] <= 0 ) {
    delete mydb.cnt[db.tag];
    return db.close();
  }
  return null;
};

这样,每次您要进行db.each()或db.save()之类的调用时,都将使用这些方法来确保db在工作时准备就绪,并在完成时关闭。

OP中的示例:

foo = db.collection('foo');

mydb.open(db); // *** Add here to init the counter.**  
foo.find({},function(err,cursor)
{
  if( err ) throw err; 
  cursor.each(function (err, doc)
  {
    if( err ) throw err;
    if (doc != null) {
      doc.newkey = 'foo';
      mydb.open(db); // *** Add here to prevent from closing prematurely **
      foo.save(doc, function(err,count) {
        if( err ) throw err;
        mydb.close(db); // *** Add here to close when done. **
      }); 
    } else {
      mydb.close(db); // *** Close like this instead. **
    }
  });
});

现在,假设每个倒数第二个回调都通过mydb.open()进行,而每个倒数第二个回调都通过mydb.close()...。因此,当然,让我知道这是否是一个问题。

因此:将mydb.open(db)放在db调用之前,并将mydb.close(db)放在回调的返回点或db调用之后(取决于调用类型)。

在我看来,这种计数器应在db对象中维护,但这是我当前的解决方法。也许我们可以创建一个在构造函数中使用db的新对象,并包装mongodb函数以更好地处理关闭。


0

无需计数器,库或任何自定义代码的现代方式:

let MongoClient = require('mongodb').MongoClient;
let url = 'mongodb://yourMongoDBUrl';
let database = 'dbName';
let collection = 'collectionName';

MongoClient.connect(url, { useNewUrlParser: true }, (mongoError, mongoClient) => {
   if (mongoError) throw mongoError;

   // query as an async stream
   let stream = mongoClient.db(database).collection(collection)
        .find({}) // your query goes here
        .stream({
          transform: (readElement) => {
            // here you can transform each element before processing it
            return readElement;
          }
        });

   // process each element of stream (async)
   stream.on('data', (streamElement) => {
        // here you process the data
        console.log('single element processed', streamElement);
   });

   // called only when stream has no pending elements to process
   stream.once('end', () => {
     mongoClient.close().then(r => console.log('db successfully closed'));
   });
});

在mongodb驱动程序的3.2.7版本上对其进行了测试,但根据链接,自2.0版本起可能有效


0

这是pkopac给出的答案的扩展示例,因为我不得不弄清楚其余的细节:

const client = new MongoClient(uri);
(async () => await client.connect())();

// use client to work with db
const find = async (dbName, collectionName) => {
  try {
    const collection = client.db(dbName).collection(collectionName);
    const result = await collection.find().toArray()
    return result;
  } catch (err) {
    console.error(err);
  }
}

const cleanup = (event) => { // SIGINT is sent for example when you Ctrl+C a running process from the command line.
  client.close(); // Close MongodDB Connection when Process ends
  process.exit(); // Exit with default success-code '0'.
}

process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);

这是和之间的区别链接。我必须添加,否则在命令行中运行进程时,我的节点网络服务器无法干净退出。SIGINTSIGTERMprocess.exit()Ctrl + C

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.