何时在 Nodejs 中关闭 MongoDB 数据库连接

Posted

技术标签:

【中文标题】何时在 Nodejs 中关闭 MongoDB 数据库连接【英文标题】:When to close MongoDB database connection in Nodejs 【发布时间】:2012-01-12 12:28:43 【问题描述】:

通过 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() 来释放资源?还是需要打开新的数据库连接?

【问题讨论】:

【参考方案1】:

这是一个基于计数方法的潜在解决方案(我还没有测试过,也没有错误捕获,但它应该传达了这个想法)。

基本策略是:获取需要更新多少条记录的计数,每条记录异步保存,成功回调,计数为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);
        
      );
    )
  );
);

【讨论】:

@realguess,还有一些用于并发工具的库可以帮助您完成这些工作,因此您不必管理细节。查看 async.js,例如 github.com/caolan/async @mpobrien,你能详细说明如何使用异步来解决这个问题吗? 您认为这个解决方案在 2017 年仍然有效还是您知道更好的解决方案?我正在考虑这样的事情,但是如果cursor.each(function (err, doc) 中的函数调用了一个异步函数,那么它将在回调中执行逻辑并且在each() 完成后可能需要数据库?如果在软件随后发生更改后,该回调调用另一个异步函数(我希望你明白)怎么办?【参考方案2】:

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

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

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

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

【讨论】:

这实际上给我带来了问题。有时,当我重新启动服务时,我会收到 Mongo“拓扑已破坏”错误,因为连接似乎被切断了。我做错了吗? @ifightcrime:听起来像是一个正在运行的查询,而您已经关闭了连接。取决于您是否需要完成查询。如果您有需要等待的写入,我想您必须跟踪它们是手动完成的。您可以尝试在这里找到它的工作原理:github.com/mongodb/node-mongodb-native/blob/2.1/lib/db.js#L366 @pkopac 在答案中的链接 - 对理解何时在 Nodejs 中关闭 MongoDB 数据库连接没有帮助(这是被问到的问题)。 pkopac 在 cmets 中的链接已损坏。不过,我认为这是这里最好的答案,应该标记为接受的答案...【参考方案3】:

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

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 可以设置为与 db 查询超时相同的值或更长。

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

【讨论】:

嘿,感谢您的回答,但是您需要将 clearInterval 从 closeIdleDb 更改为 checker :)。这真的帮助了我 很有趣! 简单快捷的解决方案。谢谢【参考方案4】:

这是我想出的解决方案。它避免使用 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 部分怎么办?他们不会发现它关闭了吗?【参考方案5】:

这里是the answer given by 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);

这是link to the difference betweenSIGINTSIGTERM。 我必须添加process.exit(),否则我的节点网络服务器在命令行运行进程中执行Ctrl + C 时没有干净地退出。

【讨论】:

【参考方案6】:

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

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() 之类的调用时,您都可以使用这些方法来确保数据库在工作时准备好并在完成后关闭。

来自 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()....所以,当然,如果这是个问题。

所以:在 db 调用之前放置一个 mydb.open(db),并在回调的返回点或 db 调用之后放置一个 mydb.close(db)(取决于调用类型)。

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

【讨论】:

【参考方案7】:

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

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 提供了一种在系列中调用异步函数的方法,其中在前一个函数成功完成之前不会调用后续函数。在数组/对象中的所有函数都成功完成后,它在最后提供了一个可选的回调,我在这种情况下使用它来最终关闭数据库连接。【参考方案8】:

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

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'));
   );
);

在 3.2.7 版本的 mongodb 驱动程序上测试过,但根据链接可能有效,因为version 2.0

【讨论】:

以上是关于何时在 Nodejs 中关闭 MongoDB 数据库连接的主要内容,如果未能解决你的问题,请参考以下文章

mongodb nodejs原生驱动是不是关闭连接

是否可以从 iPhone 应用程序中关闭漫游?

如何检测 MDC Snackbar 是不是在 JS 中关闭?

何时在 MYSQL 中使用 Sequelize?

如何在android中关闭数据连接?

在 Java 中关闭数据库连接