Надежное повторное подключение к MongoDB

#mongodb #node.js

#mongodb #node.js

Вопрос:

ОБНОВЛЕНИЕ: я использую версию 2.1 для драйвера, а не 3.2

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

 var MongoClient = require('mongodb').MongoClient
  , f = require('util').format;

MongoClient.connect('mongodb://localhost:27017/test', 

// Optional: uncomment if necessary
// { db: { bufferMaxEntries: 3 } },


function(err, db) {
  var col = db.collection('t');

  setInterval(function() {
    col.insert({a:1}, function(err, r) {
      console.log("insert")
      console.log(err)

      col.findOne({}, function(err, doc) {
        console.log("findOne")
        console.log(err)
      });
    })
  }, 1000)
});
  

Идея состоит в том, чтобы запустить этот скрипт, а затем остановить mongod, а затем перезапустить его.
Итак, поехали:

ТЕСТ 1: остановка mongod на 10 секунд

Остановка MongoDB на 10 секунд дает желаемый результат: он прекратит выполнение запросов на эти 10 секунд, а затем выполнит их все, как только сервер вернет ip

ТЕСТ 2: остановка mongod на 30 секунд

Ровно через 30 секунд я начинаю получать:

 { [MongoError: topology was destroyed] name: 'MongoError', message: 'topology was destroyed' }
insert

{ [MongoError: topology was destroyed] name: 'MongoError', message: 'topology was destroyed' }
  

Проблема в том, что с этого момента, когда я перезапускаю mongod, соединение не восстанавливается.

Решения?

Есть ли у этой проблемы решение? Если да, знаете ли вы, что это такое? Как только мое приложение начнет выдавать сообщение «топология была уничтожена», единственный способ заставить все снова работать — перезапустить все приложение…

Комментарии:

1. Возможно, вы подходите к этой проблеме с неправильного направления. На основе строки подключения ваше приложение подключается к экземпляру базы данных с одним узлом. Если для вашего приложения требуется время безотказной работы, вам следует настоятельно рассмотреть возможность подключения к набору реплик с несколькими узлами, несущими данные. Драйвер, если он настроен правильно, автоматически повторно подключится к новому основному узлу после перехода на другой ресурс.

Ответ №1:

Существует 2 варианта подключения, которые определяют, как драйвер mongo nodejs повторно подключается после сбоя соединения

  • Попытки повторного подключения: попытка повторного подключения # раз (по умолчанию 30 раз)
  • Повторное подключение: сервер будет ждать # миллисекунд между повторными попытками (по умолчанию 1000 мс)

ссылка на документы драйвера mongo

Это означает, что mongo будет продолжать попытки подключиться 30 раз по умолчанию и ждать 1 секунду перед каждой повторной попыткой. Именно поэтому вы начинаете видеть ошибки через 30 секунд.

Вы должны настроить эти 2 параметра в зависимости от ваших потребностей, как в этом примере.

 var MongoClient = require('mongodb').MongoClient,
    f = require('util').format;

MongoClient.connect('mongodb://localhost:27017/test', 
    {
        // retry to connect for 60 times
        reconnectTries: 60,
        // wait 1 second before retrying
        reconnectInterval: 1000
    },

    function(err, db) {
        var col = db.collection('t');

        setInterval(function() {
            col.insert({
                a: 1
            }, function(err, r) {
                console.log("insert")
                console.log(err)

                col.findOne({}, function(err, doc) {
                    console.log("findOne")
                    console.log(err)
                });
            })
        }, 1000)
    });
  

Это повторит попытку 60 раз вместо стандартных 30, что означает, что вы начнете видеть ошибки через 60 секунд, когда он прекратит попытки повторного подключения.

Примечание: если вы хотите запретить приложению / запросу ждать до истечения периода повторного подключения, вы должны передать эту опцию bufferMaxEntries: 0 . Ценой этого является то, что запросы также прерываются во время коротких перерывов в работе сети.

Комментарии:

1. Я обновил ответ версией драйвера / mongo. Все то же самое? Я спрашиваю, потому что в документе «connect» упоминается { server: ... } : mongodb.github.io/node-mongodb-native/2.1/tutorials/connect Однако документ для «Настроек подключения» (ссылка по этой самой ссылке): mongodb.github.io/node-mongodb-native/2.1/reference/ подключение/… вообще не упоминается {server: ...}

2. Я тестировал с теми же версиями, и объект options, похоже, работает с ним или без server:{ ... } него. Я думаю, это связано с устаревшими причинами. Я удалил это из ответа для простоты. Дайте мне знать, если это сработает для вас.

3. О БОЖЕ, я хотел дать награду за этот ответ, и я случайно присвоил его другому!!!

4. Вы дали это мне .. и здесь нет возможности вернуть его.😐

5. @Разработчик уже прошел 30-дневную гарантию возврата ^_ ^

Ответ №2:

package.json: "mongodb": "3.1.3"

Переподключите существующие соединения

Чтобы точно настроить конфигурацию повторного подключения для предварительно установленных подключений, вы можете изменить параметры reconnectTries / reconnectInterval (значения по умолчанию и дополнительную документацию здесь).

Повторное подключение к первоначальному соединению

При первоначальном подключении клиент mongo не подключается повторно, если он обнаруживает ошибку (см. Ниже). Я считаю, что так и должно быть, но в то же время я создал следующее обходное решение с использованием promise-retry библиотеки (которая использует экспоненциальную стратегию возврата).

 const promiseRetry = require('promise-retry')
const MongoClient = require('mongodb').MongoClient

const options = {
  useNewUrlParser: true,
  reconnectTries: 60,
  reconnectInterval: 1000,
  poolSize: 10,
  bufferMaxEntries: 0
}

const promiseRetryOptions = {
  retries: options.reconnectTries,
  factor: 1.5,
  minTimeout: options.reconnectInterval,
  maxTimeout: 5000
}

const connect = (url) => {
  return promiseRetry((retry, number) => {
    console.log(`MongoClient connecting to ${url} - retry number: ${number}`)
    return MongoClient.connect(url, options).catch(retry)
  }, promiseRetryOptions)
}

module.exports = { connect }
  

Ошибка начального подключения Mongo: failed to connect to server [db:27017] on first connect

Комментарии:

1. Привет, Ник, я хотел бы назначить клиенту некоторую глобальную переменную, поэтому могу ли я взять ту client , которая возвращается при обратном вызове? например, как изменить всю функцию для поддержки « return MongoClient.connect(url, options, (client) => {globalClient = client}).catch(повторить попытку) ` `

2. Привет @user2515512 — да, вам нужно будет либо использовать Promise.then , либо async функцию с await , чтобы получить client … так же, как и обычно, если бы вы использовали MongoClient.connect функцию.

Ответ №3:

По умолчанию драйвер Mongo будет пытаться повторно подключиться 30 раз, по одному раз в секунду. После этого он не будет пытаться повторно подключиться снова.

Вы можете установить количество повторных попыток на Number.MAX_VALUE, чтобы сохранить его повторное подключение «почти навсегда»:

     var connection = "mongodb://127.0.0.1:27017/db";
    MongoClient.connect(connection, {
      server : {
        reconnectTries : Number.MAX_VALUE,
        autoReconnect : true
      }
    }, function (err, db) {

    });
  

Ответ №4:

С помощью драйвера mongodb 3.1.10 вы можете настроить подключение следующим образом

 MongoClient.connect(connectionUrl, {
    reconnectInterval: 10000, // wait for 10 seconds before retry
    reconnectTries: Number.MAX_VALUE, // retry forever
}, function(err, res) {
    console.log('connected') 
})
  

Вам не нужно указывать autoReconnect: true , поскольку это значение по умолчанию.

Ответ №5:

Это происходит из-за того, что он мог превысить ограничение на повторное подключение. После нескольких попыток он прерывает TCP-соединение и становится нерабочим. Поэтому для этого увеличьте количество попыток, и было бы лучше, если бы вы увеличили разрыв между повторными попытками подключения.

Используйте следующие параметры:

 retryMiliSeconds {Number, default:5000}, number of milliseconds between retries.
numberOfRetries {Number, default:5}, number of retries off connection.
  

Для получения более подробной информации обратитесь к этой ссылке https://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html

Решение:

 MongoClient.connect("mongodb://localhost:27017/integration_test_?", {
    db: {
      native_parser: false,
retryMiliSeconds: 100000,
numberOfRetries: 100
    },
    server: {
      socketOptions: {
        connectTimeoutMS: 500
      }
    }
  }, callback)
  

Ответ №6:

Поведение может отличаться в зависимости от разных версий драйвера. Вы должны указать свою версию драйвера.

версия драйвера: 2.2.10 (последняя) версия mongo db: 3.0.7

Приведенный ниже код увеличит время, которое может потребоваться mongod для восстановления.

 var MongoClient = require('mongodb').MongoClient
  , f = require('util').format;

function connectCallback(err, db) {
  var col = db.collection('t');

  setInterval(function() {
    col.insert({a:1}, function(err, r) {
      console.log("insert")
      console.log(err)

      col.findOne({}, function(err, doc) {
        console.log("findOne")
        console.log(err)
      });
    })
  }, 1000)
}
var options = { server: { reconnectTries: 2000,reconnectInterval: 1000 }} 
MongoClient.connect('mongodb://localhost:27017/test',options,connectCallback);
  

2-й аргумент может использоваться для передачи параметров сервера.

Комментарии:

1. Я обновил ответ версией драйвера / mongo. Все то же самое?

2. Я спрашиваю, потому что в документе «connect» упоминается { server: ... } : mongodb.github.io/node-mongodb-native/2.1/tutorials/connect Однако документ для «Настроек подключения» (ссылка по этой самой ссылке): mongodb.github.io/node-mongodb-native/2.1/reference/ подключение/… вообще не упоминается {server: ...}

3. Даже в mongodb.github.io/node-mongodb-native/2.2/reference/connecting/… что относится к 2.2, свойство «сервер» опущено, а параметры передаются прямо из корня объекта (без вложенного server: свойства)…

Ответ №7:

Если вы использовали Mongoose для своих схем, было бы целесообразно рассмотреть мой вариант ниже, поскольку mongoose никогда не повторял попыток повторного подключения к MongoDB неявно после неудачной первой попытки.

Пожалуйста, обратите внимание, что я подключаюсь к Azure CosmosDB для MongoDB API. На вашем, возможно, на локальном компьютере.

Ниже приведен мой код.

 const mongoose = require('mongoose');

// set the global useNewUrlParser option to turn on useNewUrlParser for every connection by default.
mongoose.set('useNewUrlParser', true);
// In order to use `findOneAndUpdate()` and `findOneAndDelete()`
mongoose.set('useFindAndModify', false);

async function mongoDbPool() {
// Closure.
return function connectWithRetry() {
    // All the variables and functions in here will Persist in Scope.
    const COSMODDBUSER = process.env.COSMODDBUSER;
    const COSMOSDBPASSWORD = process.env.COSMOSDBPASSWORD;
    const COSMOSDBCONNSTR = process.env.COSMOSDBCONNSTR;

    var dbAuth = {
        auth: {
            user: COSMODDBUSER,
            password: COSMOSDBPASSWORD
        }
    };
    const mongoUrl = COSMOSDBCONNSTR   '?ssl=trueamp;replicaSet=globaldb';

    return mongoose.connect(mongoUrl, dbAuth, (err) => {
        if (err) {
            console.error('Failed to connect to mongo - retrying in 5 sec');
            console.error(err);
            setTimeout(connectWithRetry, 5000);
        } else {
            console.log(`Connected to Azure CosmosDB for MongoDB API.`);
        }
    });
};}
  

Вы можете экспортировать и повторно использовать этот модуль везде, где вам нужно подключиться к БД через внедрение зависимостей. Но вместо этого я пока покажу только, как получить доступ к подключению к базе данных.

 (async () => {
    var dbPools = await Promise.all([mongoDbPool()]);
    var mongoDbInstance = await dbPools[0]();

    // Now use "mongoDbInstance" to do what you need.
})();