Как мне управлять соединениями MongoDB в веб-приложении Node.js.

288

Я использую драйвер node-mongodb-native с MongoDB для написания веб-сайта.

У меня есть несколько вопросов о том, как управлять соединениями:

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

  2. Если нет, то хорошо ли мне открывать новое соединение при поступлении запроса и закрывать его при обработке запроса? Это дорого, чтобы открыть и закрыть соединение?

  3. Должен ли я использовать глобальный пул соединений? Я слышал, что драйвер имеет собственный пул соединений. Это хороший выбор?

  4. Если я использую пул соединений, сколько соединений нужно использовать?

  5. Есть ли другие вещи, на которые я должен обратить внимание?

Freewind
источник
@ IonicãBizãu, извините, я давно не использую nodejs и не вижу его. Спасибо за ваш комментарий ~
Freewind

Ответы:

460

Основной коммиттер для node-mongodb-native говорит :

Вы открываете do MongoClient.connect один раз, когда ваше приложение загружается, и повторно используете объект db. Это не пул одноэлементных соединений, каждый .connect создает новый пул соединений.

Итак, чтобы ответить на ваш вопрос напрямую, повторно используйте объект db, полученный в результатеMongoClient.connect() . Это обеспечивает объединение в пул и обеспечивает заметное увеличение скорости по сравнению с открытием / закрытием соединений при каждом действии БД.

Максимум
источник
3
Ссылка на MongoClient.connect () mongodb.github.io/node-mongodb-native/driver-articles/…
AndrewJM
4
Это правильный ответ. Принятый ответ очень неправильный, так как он говорит, что нужно открывать пул соединений для каждого запроса, а затем закрывать его после этого. Ужасная архитектура.
Саранш Мохапатра
7
Это правильный ответ. Боже мой, представь, что мне приходится открывать и закрывать каждый раз, когда я что-то делаю, это будет 350K в час только для моих вкладышей! Это как атаковать мой собственный сервер.
Мазияр
1
@Cracker: Если у вас есть экспресс-приложение, вы можете сохранить объект db в req.dbэтом промежуточном программном обеспечении: github.com/floatdrop/express-mongo-db
floatdrop
1
если есть несколько баз данных .. я должен открыть соединение для каждой базы данных все вместе. Если нет, можно ли открывать и закрывать при необходимости?
Аман Гупта
45

Откройте новое соединение при запуске приложения Node.js и повторно используйте существующий dbобъект соединения:

/server.js

import express from 'express';
import Promise from 'bluebird';
import logger from 'winston';
import { MongoClient } from 'mongodb';
import config from './config';
import usersRestApi from './api/users';

const app = express();

app.use('/api/users', usersRestApi);

app.get('/', (req, res) => {
  res.send('Hello World');
});

// Create a MongoDB connection pool and start the application
// after the database connection is ready
MongoClient.connect(config.database.url, { promiseLibrary: Promise }, (err, db) => {
  if (err) {
    logger.warn(`Failed to connect to the database. ${err.stack}`);
  }
  app.locals.db = db;
  app.listen(config.port, () => {
    logger.info(`Node.js app is listening at http://localhost:${config.port}`);
  });
});

/api/users.js

import { Router } from 'express';
import { ObjectID } from 'mongodb';

const router = new Router();

router.get('/:id', async (req, res, next) => {
  try {
    const db = req.app.locals.db;
    const id = new ObjectID(req.params.id);
    const user = await db.collection('user').findOne({ _id: id }, {
      email: 1,
      firstName: 1,
      lastName: 1
    });

    if (user) {
      user.id = req.params.id;
      res.send(user);
    } else {
      res.sendStatus(404);
    }
  } catch (err) {
    next(err);
  }
});

export default router;

Источник: Как открыть соединения с базой данных в приложении Node.js / Express

Константин Таркус
источник
1
Это создает одно соединение с базой данных ... если вы хотите использовать пулы, вы должны создавать / закрывать их при каждом использовании
amcdnl
15
Не по теме, это самый странный файл NodeJS, который я когда-либо видел.
Любитель
1
Никогда раньше не слышал о app.locals, но я рад, что вы познакомили меня с ними здесь
Z_z_Z
1
Мне очень помогли! Я делаю ошибку, чтобы создать / закрыть соединение с БД для каждого запроса, производительность моего приложения упала с этим.
Леандро Лима
18

Вот код, который будет управлять вашими подключениями MongoDB.

var MongoClient = require('mongodb').MongoClient;
var url = require("../config.json")["MongoDBURL"]

var option = {
  db:{
    numberOfRetries : 5
  },
  server: {
    auto_reconnect: true,
    poolSize : 40,
    socketOptions: {
        connectTimeoutMS: 500
    }
  },
  replSet: {},
  mongos: {}
};

function MongoPool(){}

var p_db;

function initPool(cb){
  MongoClient.connect(url, option, function(err, db) {
    if (err) throw err;

    p_db = db;
    if(cb && typeof(cb) == 'function')
        cb(p_db);
  });
  return MongoPool;
}

MongoPool.initPool = initPool;

function getInstance(cb){
  if(!p_db){
    initPool(cb)
  }
  else{
    if(cb && typeof(cb) == 'function')
      cb(p_db);
  }
}
MongoPool.getInstance = getInstance;

module.exports = MongoPool;

Когда вы запускаете сервер, звоните initPool

require("mongo-pool").initPool();

Затем в любом другом модуле вы можете сделать следующее:

var MongoPool = require("mongo-pool");
MongoPool.getInstance(function (db){
    // Query your MongoDB database.
});

Это основано на документации MongoDB . Взгляни на это.

Яки Кляйн
источник
3
Обновление с 5.x: var option = {numberOfRetries: 5, auto_reconnect: true, poolSize: 40, connectTimeoutMS: 30000};
Блэр
15

Управляйте пулами соединений Монго в одном автономном модуле. Этот подход обеспечивает два преимущества. Во-первых, это делает ваш код модульным и более простым для тестирования. Во-вторых, вам не нужно смешивать соединение с базой данных в объекте запроса, который НЕ является местом для объекта соединения с базой данных. (Учитывая природу JavaScript, я считаю очень опасным смешивать что-либо с объектом, созданным библиотечным кодом). Так что с этим вам нужно только рассмотреть модуль, который экспортирует два метода. connect = () => Promiseи get = () => dbConnectionObject.

С помощью такого модуля вы можете сначала подключиться к базе данных

// runs in boot.js or what ever file your application starts with
const db = require('./myAwesomeDbModule');
db.connect()
    .then(() => console.log('database connected'))
    .then(() => bootMyApplication())
    .catch((e) => {
        console.error(e);
        // Always hard exit on a database connection error
        process.exit(1);
    });

Когда в полете ваше приложение может просто позвонить, get()когда ему нужно соединение с БД.

const db = require('./myAwesomeDbModule');
db.get().find(...)... // I have excluded code here to keep the example  simple

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

// myAwesomeDbModule.js
let connection = null;

module.exports.connect = () => new Promise((resolve, reject) => {
    MongoClient.connect(url, option, function(err, db) {
        if (err) { reject(err); return; };
        resolve(db);
        connection = db;
    });
});

module.exports.get = () => {
    if(!connection) {
        throw new Error('Call connect first!');
    }

    return connection;
}
Стюарт
источник
очень полезно, именно то, что я искал!
Агуи
Еще лучше, вы можете просто избавиться от функции connect () и проверить функцию get (), чтобы увидеть, является ли соединение пустым, и вызвать connect для вас, если оно есть. У get () всегда возвращаются обещания. Так я управляю своим соединением, и оно прекрасно работает. Это использование шаблона синглтона.
java-addict301
@ java-addict301 Хотя этот подход обеспечивает более упорядоченный API, у него есть два недостатка. Во-первых, нет определенного способа проверить наличие ошибок подключения. Вы должны были бы обрабатывать эту строку везде, где бы вы ни звонили. Мне нравится рано выходить из строя с соединениями с базой данных и обычно я не позволяю приложению загружаться без подключения к базе данных. Другая проблема - пропускная способность. Поскольку у вас нет активного соединения, вам, возможно, придется подождать немного дольше при первом вызове get (), который вы не сможете контролировать. Может исказить ваши показатели отчетности.
Стюарт
1
@Stewart Способ, которым я структурирую приложения / сервисы, обычно заключается в получении конфигурации из базы данных при запуске. Таким образом, приложение не сможет запуститься, если база данных недоступна. Кроме того, поскольку первый запрос всегда запускается, нет проблем с показателями в этом дизайне. Также перебросьте исключение соединения с одним местом в синглтоне с явной ошибкой, которую он не может соединить. Так как приложению все равно нужно перехватывать ошибки базы данных при использовании соединения, это не приводит к дополнительной внутренней обработке.
java-addict301
2
Привет @ Аян. Важно отметить, что здесь, когда мы звоним, get()мы получаем пул соединений, а не одно соединение. Пул соединений, как следует из его названия, представляет собой логическую коллекцию соединений с базой данных. Если в пуле нет соединений, драйвер попытается их открыть. Как только это соединение открыто, оно используется и возвращается в пул. При следующем обращении к пулу это соединение может быть использовано повторно. Приятно то, что пул будет управлять нашими соединениями для нас, поэтому, если соединение будет сброшено, мы никогда не узнаем, так как пул откроет для нас новое.
Стюарт
11

Если у вас есть Express.js, вы можете использовать express-mongo-db для кэширования и совместного использования соединения MongoDB между запросами без пула (поскольку в принятом ответе говорится, что это правильный способ поделиться соединением).

Если нет - вы можете посмотреть на его исходный код и использовать его в другом фреймворке.

floatdrop
источник
6

Я использовал generic-pool с подключениями redis в своем приложении - я очень рекомендую его. Он универсален, и я точно знаю, что он работает с mysql, поэтому я не думаю, что у вас возникнут какие-либо проблемы с ним и монго

https://github.com/coopernurse/node-pool

ControlAltDel
источник
Mongo уже выполняет пул соединений в драйвере, однако я сопоставил мои монго-соединения с интерфейсом, который соответствует пулу узлов, таким образом, все мои соединения следуют одному и тому же шаблону, хотя в случае монго очистка не на самом деле вызвать что-нибудь.
Tracker1
4

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

// db.service.js
import { MongoClient } from "mongodb";
import database from "../config/database";

const dbService = {
  db: undefined,
  connect: callback => {
    MongoClient.connect(database.uri, function(err, data) {
      if (err) {
        MongoClient.close();
        callback(err);
      }
      dbService.db = data;
      console.log("Connected to database");
      callback(null);
    });
  }
};

export default dbService;

мой пример App.js

// App Start
dbService.connect(err => {
  if (err) {
    console.log("Error: ", err);
    process.exit(1);
  }

  server.listen(config.port, () => {
    console.log(`Api runnning at ${config.port}`);
  });
});

и использовать его где угодно

import dbService from "db.service.js"
const db = dbService.db
TheAlemazing
источник
1
Если Монго не может подключиться, MongoClient.close () выдает ошибку. Но хорошее решение для оригинальной проблемы.
Химаншу
2

Я реализовал приведенный ниже код в своем проекте, чтобы реализовать пул соединений в моем коде, чтобы он создал минимальное соединение в моем проекте и повторно использовал доступное соединение

/* Mongo.js*/

var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/yourdatabasename"; 
var assert = require('assert');

var connection=[];
// Create the database connection
establishConnection = function(callback){

                MongoClient.connect(url, { poolSize: 10 },function(err, db) {
                    assert.equal(null, err);

                        connection = db
                        if(typeof callback === 'function' && callback())
                            callback(connection)

                    }

                )



}

function getconnection(){
    return connection
}

module.exports = {

    establishConnection:establishConnection,
    getconnection:getconnection
}

/*app.js*/
// establish one connection with all other routes will use.
var db = require('./routes/mongo')

db.establishConnection();

//you can also call with callback if you wanna create any collection at starting
/*
db.establishConnection(function(conn){
  conn.createCollection("collectionName", function(err, res) {
    if (err) throw err;
    console.log("Collection created!");
  });
};
*/

// anyother route.js

var db = require('./mongo')

router.get('/', function(req, res, next) {
    var connection = db.getconnection()
    res.send("Hello");

});
Адитья Пармар
источник
1

Лучший подход для реализации пула соединений - вы должны создать одну переменную глобального массива, которая будет содержать имя базы данных с объектом соединения, возвращенным MongoClient, а затем повторно использовать это соединение всякий раз, когда вам нужно связаться с базой данных.

  1. В вашем Server.js определите var global.dbconnections = [];

  2. Создайте сервис именования connectionService.js. У него будет 2 метода getConnection и createConnection. Поэтому, когда пользователь вызовет getConnection (), он найдет детали в глобальной переменной соединения и вернет детали соединения, если уже существует, иначе он вызовет createConnection () и вернет детали соединения.

  3. Вызовите этот сервис, используя db_name, и он вернет объект соединения, если он уже есть, он создаст новое соединение и вернет его вам.

Надеюсь, поможет :)

Вот код connectionService.js:

var mongo = require('mongoskin');
var mongodb = require('mongodb');
var Q = require('q');
var service = {};
service.getConnection = getConnection ;
module.exports = service;

function getConnection(appDB){
    var deferred = Q.defer();
    var connectionDetails=global.dbconnections.find(item=>item.appDB==appDB)

    if(connectionDetails){deferred.resolve(connectionDetails.connection);
    }else{createConnection(appDB).then(function(connectionDetails){
            deferred.resolve(connectionDetails);})
    }
    return deferred.promise;
}

function createConnection(appDB){
    var deferred = Q.defer();
    mongodb.MongoClient.connect(connectionServer + appDB, (err,database)=> 
    {
        if(err) deferred.reject(err.name + ': ' + err.message);
        global.dbconnections.push({appDB: appDB,  connection: database});
        deferred.resolve(database);
    })
     return deferred.promise;
} 
RRPatel
источник
0

mongodb.com -> новый проект -> новый кластер -> новая коллекция -> подключиться -> IP-адрес: 0.0.0.0/0 & db cred -> подключить ваше приложение -> скопировать строку подключения и вставить в файл .env вашего узла приложение и убедитесь, что заменили "" на действительный пароль для пользователя, а также замените "/ test" на ваше имя базы данных

создать новый файл .env

CONNECTIONSTRING=x --> const client = new MongoClient(CONNECTIONSTRING) 
PORT=8080 
JWTSECRET=mysuper456secret123phrase
anoNewb
источник
0

При использовании Express есть еще один более простой метод, который заключается в использовании встроенной функции Express для обмена данными между маршрутами и модулями в вашем приложении. Есть объект с именем app.locals. Мы можем прикрепить к нему свойства и получить к нему доступ внутри наших маршрутов. Чтобы использовать его, создайте экземпляр вашего монго-соединения в файле app.js.

var app = express();

MongoClient.connect('mongodb://localhost:27017/')
.then(client =>{
  const db = client.db('your-db');
  const collection = db.collection('your-collection');
  app.locals.collection = collection;
});
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // view engine setup
app.set('views', path.join(__dirname, 'views'));

Это соединение с базой данных или любые другие данные, которыми вы хотите поделиться вокруг модулей вашего приложения, теперь могут быть доступны по вашим маршрутам, req.app.localsкак показано ниже, без необходимости создавать и требовать дополнительных модулей.

app.get('/', (req, res) => {
  const collection = req.app.locals.collection;
  collection.find({}).toArray()
  .then(response => res.status(200).json(response))
  .catch(error => console.error(error));
});

Этот метод гарантирует, что у вас есть соединение с базой данных, открытое на время вашего приложения, если вы не решите закрыть его в любое время. Он легко доступен req.app.locals.your-collectionи не требует создания каких-либо дополнительных модулей.

Hoppo
источник