Коли закрити підключення до бази даних MongoDB у Nodejs


79

Робота з Nodejs та MongoDB через власний драйвер Node 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. Але чи я роблю тут щось погане? Який найкращий спосіб вирішити подібну ситуацію? Чи db.close()потрібно використовувати для звільнення ресурсів? Або потрібно відкрити нове з’єднання дБ?

Відповіді:


26

Ось потенційне рішення, засноване на підході підрахунку (я його не тестував, і немає помилок, але це повинно передати ідею).

Основною стратегією є: Отримати підрахунок кількості записів, які потрібно оновити, зберегти кожен запис асинхронно та зворотний виклик про успіх, що зменшить кількість і закриє БД, якщо підрахунок досягне 0 (коли закінчується останнє оновлення). За допомогою {safe:true}ми можемо гарантувати, що кожне оновлення буде успішним.

Сервер mongo використовуватиме по одному потоку для кожного з’єднання, тому добре або а) закрити невикористані з’єднання, або б) об’єднати / повторно використовувати їх.

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, також є бібліотеки для одночасного використання, які можуть допомогти вам зробити це, тому вам не доведеться керувати деталями. перевірити async.js, наприклад github.com/caolan/async
mpobrien

@mpobrien, не могли б Ви детальніше розповісти, як використовувати асинхронізацію для вирішення цієї проблеми?
Марсіо Пайва

Ви вважаєте, що ці рішення все ще зберігаються у 2017 році, чи ви знаєте щось краще? Я думав про щось подібне, але що, якщо функція у cursor.each(function (err, doc) {виклику функції асинхронізації, яка, таким чином, буде виконувати логіку у зворотному виклику і потенційно потребуватиме бази даних після each()закінчення? А що, якщо після подальших змін у програмному забезпеченні цей зворотний виклик викликає іншу функцію асинхронізації (сподіваюся, ви зрозуміли цю ідею)?
водянистий

20

Найкраще використовувати об’єднане з’єднання, а потім зателефонувати db.close () у функції очищення в кінці життя програми:

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

Див. Http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html

Трохи стара нитка, але все одно.


Це насправді викликає у мене проблеми. Іноді, коли я перезапускаю службу, я отримую помилки Mongo "Топологію було знищено", оскільки з'єднання, здається, перериваються. Я щось роблю не так?
ifightcrime

1
@ifightcrime: звучить як запущений запит, поки ви перервали з’єднання. Залежить від того, чи потрібні вам запити для завершення. Якщо у вас є записи, вам потрібно почекати, я думаю, вам доведеться відстежувати, що вони робляться вручну. Ви можете спробувати дізнатися, як саме це працює, тут: github.com/mongodb/node-mongodb-native/blob/2.1/lib/db.js#L366
pkopac

Посилання @ pkopac у відповіді - чи не так корисно зрозуміти, коли слід закрити підключення до бази даних MongoDB у Nodejs (що і задається) Посилання pkopac у коментарях порушено. Тим не менше, я вважаю, що це найкраща відповідь тут, і її слід позначити як прийняту відповідь ...
ЯК

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 може бути встановлено як таке саме значення, як час очікування запиту db або довше.

Це не дуже елегантно, але я не можу придумати кращого способу зробити це. Я використовую NodeJs для запуску сценарію, який запитує MongoDb та Mysql, і сценарій зависає там назавжди, якщо підключення до бази даних не закриті належним чином.


1
Привіт, я ціную відповідь, однак вам потрібно змінити clearInterval з closeIdleDb на checker :). Це мені справді допомогло
RNikoopour

Доволі цікаво!
водянистий

Просте і швидке рішення. Дякую
Педрам Маранді

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

Я придумав рішення, яке передбачає такий лічильник. Це не залежить від виклику count () і не очікує тайм-ауту. Він закриє db після вичерпання всіх документів у кожному ().

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 для кращої обробки закриття.


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

Сучасний спосіб зробити це без лічильників, бібліотек або будь-якого спеціального коду:

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, але за посиланням може діяти з версії 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);

Ось посилання на різницю між SIGINT і SIGTERM. Мені довелося додати process.exit(), інакше мій веб-сервер вузла не вийшов чисто, коли робив це Ctrl + Cв запущеному процесі в командному рядку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.