Khi nào đóng kết nối cơ sở dữ liệu MongoDB trong Nodejs


79

Làm việc với Nodejs và MongoDB thông qua trình điều khiển gốc Node MongoDB. Cần truy xuất một số tài liệu và thực hiện sửa đổi, sau đó lưu chúng lại ngay. Đây là một ví dụ:

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

Với tính chất không đồng bộ, nếu quá trình cập nhật tài liệu lâu hơn, thì khi con trỏ đến cuối tài liệu, kết nối cơ sở dữ liệu sẽ bị đóng. Không phải tất cả các bản cập nhật đều được lưu vào cơ sở dữ liệu.

Nếu db.close()bỏ qua, tất cả các tài liệu được cập nhật chính xác, nhưng ứng dụng bị treo, không bao giờ thoát.

Tôi thấy một bài đăng đề xuất sử dụng bộ đếm để theo dõi số lượng cập nhật, khi trở về 0, sau đó đóng db. Nhưng tôi có làm gì sai ở đây không? Cách tốt nhất để xử lý tình huống này là gì? Có db.close()phải được sử dụng để giải phóng tài nguyên? Hay một kết nối db mới cần phải mở?

Câu trả lời:


26

Đây là một giải pháp tiềm năng dựa trên phương pháp đếm (Tôi chưa thử nghiệm nó và không có lỗi nào mắc kẹt, nhưng nó sẽ truyền đạt ý tưởng).

Chiến lược cơ bản là: Nhận số lượng bản ghi cần được cập nhật, lưu từng bản ghi không đồng bộ và lệnh gọi lại khi thành công, sẽ giảm số lượng và đóng DB nếu số lượng đạt đến 0 (khi bản cập nhật cuối cùng kết thúc). Bằng cách sử dụng, {safe:true}chúng tôi có thể đảm bảo rằng mỗi bản cập nhật thành công.

Máy chủ mongo sẽ sử dụng một luồng cho mỗi kết nối, vì vậy, tốt nhất là a) đóng các kết nối không sử dụng hoặc b) gộp / sử dụng lại chúng.

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

6
@realguess, ngoài ra còn có các libs cho utils đồng thời có thể giúp bạn thực hiện công việc này để bạn không phải quản lý chi tiết. xem async.js, ví dụ github.com/caolan/async
mpobrien

@mpobrien, bạn có thể nói rõ hơn về cách sử dụng async để giải quyết vấn đề này không?
Márcio Paiva

Bạn có nghĩ rằng giải pháp này vẫn được duy trì trong năm 2017 hay bạn có biết điều gì tốt hơn không? Tôi đã nghĩ về điều gì đó như thế này, nhưng điều gì sẽ xảy ra nếu hàm trong cursor.each(function (err, doc) {gọi một hàm không đồng bộ, do đó sẽ thực thi logic trong một lệnh gọi lại và có khả năng cần cơ sở dữ liệu sau khi each()kết thúc? Và điều gì sẽ xảy ra nếu sau những thay đổi tiếp theo trong phần mềm, lệnh gọi lại đó gọi một hàm không đồng bộ khác (tôi hy vọng bạn hiểu rõ)?
nhiều nước

20

Tốt nhất là sử dụng kết nối gộp và sau đó gọi hàm db.close () trong chức năng dọn dẹp ở cuối vòng đời ứng dụng của bạn:

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

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

Một chủ đề hơi cũ, nhưng dù sao.


Điều này thực sự gây ra vấn đề cho tôi. Đôi khi khi tôi khởi động lại dịch vụ của mình, tôi nhận được lỗi "Cấu trúc liên kết bị phá hủy" của Mongo vì các kết nối dường như bị cắt. Tôi có làm điều gì sai?
ifightcrime

1
@ifightcrime: nghe giống như một truy vấn đang chạy, trong khi bạn đã đóng kết nối. Tùy thuộc, bạn có cần các truy vấn để hoàn thành hay không. Nếu bạn có những bài viết mà bạn cần chờ đợi, tôi đoán bạn phải theo dõi xem chúng được thực hiện theo cách thủ công hay không. Bạn có thể thử tìm cách hoạt động chính xác của nó tại đây: github.com/mongodb/node-mongodb-native/blob/2.1/lib/db.js#L366
pkopac,

Liên kết của @ pkopac trong câu trả lời - không hữu ích khi hiểu khi nào đóng kết nối cơ sở dữ liệu MongoDB trong Nodejs (đây là câu hỏi được hỏi). liên kết của pkopac trong các bình luận đã bị hỏng. Tuy nhiên, tôi nghĩ rằng đây là câu trả lời tốt nhất ở đây và nên được đánh dấu là câu trả lời được chấp nhận ...
AS

6

Tôi thấy rằng sử dụng bộ đếm có thể áp dụng cho tình huống đơn giản, nhưng có thể khó trong các tình huống phức tạp. Đây là một giải pháp mà tôi đưa ra bằng cách đóng kết nối cơ sở dữ liệu khi kết nối cơ sở dữ liệu không hoạt động:

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

Đây có thể là một giải pháp chung cho bất kỳ Kết nối cơ sở dữ liệu nào. maxDbIdleTime có thể được đặt thành giá trị giống như thời gian chờ truy vấn db hoặc lâu hơn.

Điều này không được thanh lịch cho lắm, nhưng tôi không thể nghĩ ra cách tốt hơn để làm điều này. Tôi sử dụng NodeJs để chạy một tập lệnh truy vấn MongoDb và Mysql, và tập lệnh bị treo ở đó mãi mãi nếu các kết nối cơ sở dữ liệu không được đóng đúng cách.


1
Này, tôi đánh giá cao câu trả lời tuy nhiên bạn cần thay đổi clearInterval từ closeIdleDb thành trình kiểm tra :). Điều này thực sự giúp tôi ra ngoài
RNikoopour

Khá thú vị!
nhiều nước

Giải pháp đơn giản và nhanh chóng. Cảm ơn bạn
Pedram marandi

2

Đây là một giải pháp tôi đã đưa ra. Nó tránh sử dụng toArray và nó khá ngắn và ngọt ngào:

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();
    });
  });
});

Điều gì sẽ xảy ra nếu các lệnh gọi không đồng bộ khác cuối cùng ghi vào cơ sở dữ liệu nằm trong // do stuff herephần này? Họ sẽ thấy nó đóng cửa?
nhiều nước

1

Tôi đã đưa ra một giải pháp liên quan đến một bộ đếm như thế này. Nó không phụ thuộc vào một cuộc gọi count () cũng như không đợi hết thời gian. Nó sẽ đóng db sau khi tất cả các tài liệu trong mỗi () hết.

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;
};

Vì vậy, mỗi lần bạn thực hiện một cuộc gọi như db.each () hoặc db.save (), bạn sẽ sử dụng các phương pháp này để đảm bảo db luôn sẵn sàng trong khi làm việc và đóng khi hoàn tất.

Ví dụ từ 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. **
    }
  });
});

Bây giờ, điều này giả định rằng lệnh gọi lại thứ hai đến cuối cùng từ mỗi lệnh thực hiện nó thông qua mydb.open () trước khi lệnh gọi lại cuối cùng từ mỗi lệnh chuyển đến mydb.close () .... vì vậy, tất nhiên, hãy cho tôi biết nếu đây là một vấn đề.

Vì vậy: đặt mydb.open (db) trước lệnh gọi db và đặt mydb.close (db) tại điểm quay lại của lệnh gọi lại hoặc sau lệnh gọi db (tùy thuộc vào loại cuộc gọi).

Có vẻ như với tôi rằng loại bộ đếm này nên được duy trì trong đối tượng db nhưng đây là cách giải quyết hiện tại của tôi. Có lẽ chúng ta có thể tạo một đối tượng mới lấy db trong hàm tạo và bọc các hàm mongodb để xử lý việc đóng tốt hơn.


1

Dựa trên những gợi ý từ @mpobrien ở trên, tôi đã tìm thấy các async mô-đun sẽ vô cùng hữu ích trong vấn đề này. Đây là một mẫu ví dụ mà tôi đã áp dụng:

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();
    }
);

Bạn có thể thêm một số giải thích về cách hoạt động và giải quyết vấn đề này không? Đối với những người không biết async như tôi.
nhiều nước

@watery, async.series cung cấp một cách gọi các hàm không đồng bộ trong một chuỗi, trong đó một hàm tiếp theo không được gọi cho đến khi hàm trước đó hoàn thành thành công. Nó cung cấp một lệnh gọi lại tùy chọn ở cuối, sau khi tất cả các hàm trong mảng / đối tượng đã hoàn tất thành công, mà tôi đang sử dụng trong trường hợp này để cuối cùng đóng kết nối cơ sở dữ liệu.
Andrew Kirk

0

Cách hiện đại để thực hiện việc này mà không cần bộ đếm, thư viện hoặc bất kỳ mã tùy chỉnh nào:

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

Đã thử nghiệm nó trên phiên bản 3.2.7 của trình điều khiển mongodb nhưng theo liên kết có thể hợp lệ kể từ phiên bản 2.0


0

Đây là một ví dụ mở rộng cho câu trả lời do pkopac đưa ra , vì tôi phải tìm ra phần còn lại của các chi tiết:

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

Đây là một liên kết đến sự khác biệt giữa SIGINTSIGTERM. Tôi đã phải thêm process.exit(), nếu không máy chủ web nút của tôi không thoát sạch khi thực hiện Ctrl + Cquy trình đang chạy trong dòng lệnh.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.