Обработка тайм-аутов с помощью 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, поэтому я думаю, что это может быть лучшим решением, даже если я против того, чтобы что-то летало магически в глобальном пространстве.