Обработка тайм-аутов с помощью Node.js и mongodb

В настоящее время я тестирую, как какой-то код стоит против следующего сценария:

  • Node.js запущено и успешно устанавливает соединение с mongodb
  • После успешной установки соединения сервер mongodb умирает и все последующие запросы терпят неудачу

Для этого у меня есть следующий код, который использует официальный драйвер (здесь: https://github.com/mongodb/node-mongodb-native):

MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db) {
app.get('/test', function(req, res) {
    db.collection('users', function (err, collection) {
        console.log(err);
        if (err) {
            // ## POINT 1 ##
            // Handle the error
        }
        else {
            collection.find({ 'username': username }, { timeout: true }).toArray(function(err, items) {
                console.log(err);
                if (err) {
                    // ## POINT 2 ##
                    // Handle the error
                }
                else {
                    if (items.length > 0) {
                        // Do some stuff with the document that was found
                    }
                    else {
                        // Handle not finding the document
                    }
                }
            }); 
        }
    });
});

});

Поскольку сервер mongodb больше не работает при обработке запроса, я сделал предположение, что в тех точках, которые я обозначил ## POINT 1 ## или ## POINT 2 ##, он вернется ошибка, указывающая таймаут; это, однако, не так.

Я попробовал несколько различных настроек (в том числе тот, который вы видите здесь, который явно позволяет курсору на время ожидания), однако я никак не могу его включить. В каждой конфигурации, которую я пробовал Node.js, просто будет ждать, пока операция find() вернется к обратному вызову, и она никогда этого не сделает.

Если я запустил приложение Node.js перед запуском mongodb, он поймал ошибку в правильном обратном вызове соединения, но если соединение замирает после этого, оно никак не обрабатывает его.

Есть ли параметр, который у меня отсутствует, или нет способа обнаружить, что соединения завершаются после того, как они были установлены?

Изменить: просто чтобы быть ясным, переменная имени пользователя, используемая в методе поиска, фактически объявлена ​​в моем полном коде, код, который я поставил в этом сообщении, является сокращенной версией, чтобы проиллюстрировать структуру и проверку ошибок.

Ответы

Ответ 1

UPD:
Основываясь на этом сообщении, похоже, что они развернули исправление, которое будет делать то же самое, что и мы здесь. Не уверен, что это уже в пределах npm (15.10.13). https://github.com/mongodb/node-mongodb-native/issues/1092#ref-commit-2667d13

После некоторого расследования мне удалось понять, что там происходит:
Каждый раз, когда вы вызываете какой-либо метод для работы с базой данных (найти, обновлять, вставлять и т.д.), Он создает курсор, который имеет собственный идентификатор и регистрируется в EventEmitter из Db для последующего вызова. Тем временем он регистрируется в объекте _notReplied в пределах одного CallBackStore.

Но как только соединение закрыто, я не смог найти что-либо, что бы перебирать курсоры _notReplied и запускать их с ошибками или любой логикой с таймерами (она все еще может быть где-то там). Таким образом, мне удалось написать небольшую работу, которая заставит триггеры курсоров с ошибкой, когда DB испускает событие close:

new mongodb.Db('testdb', new mongodb.Server('localhost', 27017, { }), { safe: true }).open(function (err, db) {
  if (!err) {
    db.on('close', function() {
      if (this._callBackStore) {
        for(var key in this._callBackStore._notReplied) {
          this._callHandler(key, null, 'Connection Closed!');
        }
      }
    });

    // ...

  } else {
    console.log(err)
  }
});

Я рекомендую использовать первый подход вместо MongoClient. Причин мало: например, когда вы закрываете соединение, а затем вызываете .find, он будет правильно запускать ошибку в обратном вызове, тогда как с MongoClient этого не будет.

Если вы используете MongoClient:

MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db) {
  if (!err) {
    db.on('close', function() {
      if (this._callBackStore) {
        for(var key in this._callBackStore._notReplied) {
          this._callHandler(key, null, 'Connection Closed!');
        }
      }
    });

    // ...

  } else {
    console.log(err);
  }
});

Что это будет делать? Как только соединение будет закрыто, он будет выполнять итерацию через все _notReplied курсоры и события триггера для них с ошибкой Connection Closed!.

Тестовый пример:

items.find({ }).toArray(function(err, data) {
  if (!err) {
    console.log('Items found successfully');
  } else {
    console.log(err);
  }
});
db.close();

Это заставит закрыть соединение с базой данных и вызвать событие close, которое вы обрабатываете ранее, и убедитесь, что курсор будет закрыт.

UPD: Я добавил проблему в GitHub: https://github.com/mongodb/node-mongodb-native/issues/1092, мы увидим, что они говорят по этому поводу.

Ответ 2

У меня была такая же проблема, и я нашел эту страницу из Google. Но ваш выбранный ответ не разрешил проблему, и он такой же, как вы, this._callBackStore не может использовать

но я попытался обернуть Mongo, и кажется, что он работает нормально

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

var mongo = {};
mongo.init = function() {
  MongoClient.connect('mongodb://localhost:27017/testdb', function(err, db) {
    if (err) {
      mongo.DB = '';
    } else {
      mongo.DB = db;
    }
    db.on('close', function() {
      mongo.DB = '';
    });
    db.on('reconnect', function() {
      mongo.DB = db;
    });
  }
}
                      
mongo.getdb = function(callback) {
    if (mongo.DB) {
      callback(null, mongo.DB);
    } else {
      callback('can not connect to db', null);
    }
}
module.exports = mongo;

Ответ 3

После некоторого дальнейшего исследования кажется, что вы не можете указывать "автономные" таймауты, например, в описанном выше сценарии. Единственный тайм-аут, который может быть указан, - это тот, который информирует сервер о необходимости таймаута курсора после 10 минут бездействия, однако, как и в сценарии выше, подключение к серверу не работает, это не работает.

Для справки я нашел здесь информацию: https://github.com/mongodb/node-mongodb-native/issues/987#issuecomment-18915263, по которому я считал себя одним из основных участников проекта.

Ответ 4

Я делаю апи с Хапи и Мондомбом (без мугуса). Особенности:

  • Начать отвечать на запрос API, только если mongo db доступен
  • Прекратить отвечать, если mongo умирает во время цикла
  • Повторно запустите, когда снова появится манго
  • Поддерживать одно соединение для всех запросов

Объединив некоторые идеи из других ответов, и этот пост https://productbuilder.wordpress.com/2013/09/06/using-a-single-global-db-connection-in-node-js/ мой подход таков:

server.js

Utilities.initializeDb(() => {
    server.start((err) => {
        if (err) throw err;
        console.log('Server running at:', server.info.uri);
    });
}, () => {
    server.stop((err) => {
        if (err) throw err;
        console.log('Server stopped');
    });
});

Utilities.js

"use strict";

const MongoClient = require('mongodb').MongoClient;
const MongoUrl = 'mongodb://localhost:27017/db';

export const Utilities = {
    initializeDb: (next, onCrash) => {

        const ConnectToDatabase = (params) => {
            MongoClient.connect(MongoUrl, (err, db) => {
                if (err !== null) {
                    console.log('#t4y4542te Can not connect to mongo db service. Retry in 2 seconds. Try #' + params.retry);
                    console.error(err);
                    setTimeout(() => {
                        ConnectToDatabase({retry: params.retry + 1});
                    }, 2000);
                } else {

                    db.on('close', () => {
                        onCrash();
                        console.log('#21df24sf db crashed!');
                        ConnectToDatabase({retry: 0});
                    });
                    global.db = global.db || db;
                    next();
                }
            });
        };

        ConnectToDatabase({retry: 0});

    }
};

Я экспортирую db-соединение в глобальное пространство. Это похоже на не лучшее решение, но у меня были проекты, в которых соединение db было передано как параметр ко всем модулям, и это засасывало больше. Возможно, должен быть какой-то модульный подход, при котором вы импортируете соединение db там, где оно вам нужно, но в моей ситуации я нуждаюсь в нем почти везде, мне нужно будет написать этот оператор include в большинстве файлов. Этот API бессмыслен без подключения к db, поэтому я думаю, что это может быть лучшим решением, даже если я против того, чтобы что-то летало магически в глобальном пространстве.