2011-12-04 30 views
62

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à sửa đổi, sau đó lưu chúng lại ngay. Đây là một ví dụ:Khi nào đóng kết nối cơ sở dữ liệu MongoDB trong Nodejs

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 mất nhiều thời gian hơn, thì 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 bỏ qua db.close(), tất 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 giảm 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ý loại tình huống này là gì? Có phải db.close() phải được sử dụng để giải phóng tài nguyên không? Hay một kết nối db mới cần mở?

Trả lời

23

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

Chiến lược cơ bản là: Thu thập số lượng bản ghi cần cập nhật, lưu từng bản ghi không đồng bộ và gọi lại thành công, sẽ giảm số lượng và đóng DB nếu số đếm đến 0 (khi cập nhật lần cuối 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 cập nhật đều thành công.

Máy chủ mongo sẽ sử dụng một luồng cho mỗi kết nối, do đó, tốt nhất là sử dụng các kết nối không sử dụng gần hoặc b) nhóm/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); 
     } 
     }); 
    }) 
    }); 
}); 
+5

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

+0

@mpobrien, bạn có thể giải thích cách sử dụng async để giải quyết vấn đề này không? –

+0

Bạn có nghĩ rằng các giải pháp này vẫn giữ trong năm 2017 hoặc bạn có biết điều gì tốt hơn không? Tôi đã suy nghĩ về một cái gì đó như thế này, nhưng nếu hàm trong 'cursor.each (function (err, doc) {' gọi một hàm async, do đó sẽ thực thi logic trong một cuộc gọi lại và có khả năng cần cơ sở dữ liệu sau 'each() Và nếu sau những thay đổi tiếp theo trong phần mềm, hàm callback đó gọi một hàm async khác (tôi hy vọng bạn có được ý tưởng) – watery

4

Tôi thấy rằng việc sử dụng bộ đếm có thể áp dụng cho trường hợp đơn giản, nhưng có thể khó trong các tình huống phức tạp. Đây là 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 bị nhàn rỗi:

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à 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 bằng giá trị giống như thời gian chờ truy vấn db hoặc lâu hơn.

Đây không phải là rất thanh lịch, nhưng tôi không thể nghĩ ra một cách tốt hơn để làm điều này. Tôi sử dụng NodeJs để chạy một kịch bản truy vấn MongoDb và Mysql, và kịch bản 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

Hey, tôi đánh giá cao câu trả lời tuy nhiên bạn cần thay đổi clearInterval từ closeIdleDb thành checker :). Điều này thực sự đã giúp tôi ra khỏi – RNikoopour

+0

Khá thú vị! – watery

0

Tôi đã đưa ra giải pháp liên quan đến bộ đếm như thế này. Nó không phụ thuộc vào một cuộc gọi count() cũng không chờ đợi một thời gian ra ngoài. 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 khi bạn thực hiện 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 sẵn sàng 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 thứ hai để gọi lại cuối cùng từ mỗi làm cho nó thông qua các mydb.open() trước khi gọi lại cuối cùng từ mỗi đi vào mydb.close(). ... tất nhiên, hãy cho tôi biết nếu đây là vấn đề.

Vì vậy: hãy đặt một mydb.open (db) trước cuộc gọi db và đặt một mydb.đóng (db) tại điểm trả về của cuộc gọi lại hoặc sau cuộc gọi db (tùy thuộc vào loại cuộc gọi).

Dường 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 một db trong hàm dựng và bọc các hàm mongodb để xử lý tốt hơn.

13

Tốt nhất là nên sử dụng kết nối gộp và sau đó gọi db.close() trong chức năng dọn dẹp vào cuối cuộc đời của ứ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ủ đề cũ chút, nhưng dù sao .

+0

Điều này thực sự gây ra vấn đề cho tôi. Thỉnh thoảng khi tôi khởi động lại dịch vụ của mình, tôi nhận được lỗi "Topology bị hủy" 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: có vẻ giống như một truy vấn đang chạy, trong khi bạn đã đóng kết nối. Phụ thuộc, cho dù bạn cần các truy vấn để hoàn thành. Nếu bạn đã viết, bạn cần phải chờ đợi, tôi đoán bạn phải theo dõi rằng họ đang thực hiện bằng tay. Bạn có thể thử tìm cách hoạt động chính xác tại đây: https://github.com/mongodb/node-mongodb-native/blob/2.1/lib/db.js#L366 – pkopac

1

Dựa trên đề xuất từ ​​@mpobrien ở trên, tôi đã tìm thấy mô-đun async cực kỳ hữu ích trong lĩnh vực này. Dưới đây là ví dụ mẫu mà tôi đã chấp nhận:

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

Bạn có thể thêm một số giải thích về cách thức hoạt động và giải quyết vấn đề? Đối với những người như tôi, những người không biết không đồng bộ. – watery

+0

@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 đó hàm tiếp theo không được gọi cho đến khi hàm trước đã hoàn tất thành công. Nó cung cấp một tùy chọn gọi lại ở cuối, sau khi tất cả các chức năng trong mảng/đối tượng đã hoàn thành 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. –

0

Đây là 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(); 
    }); 
    }); 
}); 
+0

Điều gì sẽ xảy ra nếu các cuộc gọi async khác mà cuối cùng ghi vào cơ sở dữ liệu nằm trong phần '// do stuff here'? Họ sẽ không tìm thấy nó đóng cửa? – watery

Các vấn đề liên quan