Як керувати підключеннями MongoDB у веб-програмі Node.js?


288

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

У мене є питання щодо керування з'єднаннями:

  1. Чи достатньо використовувати лише одне підключення MongoDB для всіх запитів? Чи є якісь проблеми з продуктивністю? Якщо ні, чи можу я встановити глобальне з'єднання для використання у всій програмі?

  2. Якщо ні, чи добре, якщо я відкриваю нове з'єднання, коли надходить запит, і закриваю його при обробці запиту? Чи дорого відкривати та закривати зв’язок?

  3. Чи варто використовувати пул глобальних з'єднань? Я чую, що у водія є власний пул підключення. Це гарний вибір?

  4. Якщо я використовую пул з'єднань, скільки з'єднань слід використовувати?

  5. Чи є інші речі, які я повинен помітити?


@ IonicãBizãu, вибачте, я довго не використовую nodejs, що не бачу його. Дякуємо за Ваш коментар ~
Freewind

Відповіді:


459

Основний виконувальник node-mongodb-native каже :

Ви відкриваєте програму 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цьому посередництві
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

Керуйте пулами підключення mongo в одному самодоступному модулі. Такий підхід забезпечує дві переваги. По-перше, він зберігає ваш код модульним і простішим для тестування. По-друге, ви не змушені змішувати підключення до вашої бази даних в об'єкт запиту, який НЕ є місцем для об'єкта підключення до бази даних. (Враховуючи природу 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

Якщо ви налаштуєте модуль db таким же чином, як наступний, не тільки ви матимете спосіб гарантувати, що ваша програма не завантажиться, якщо ви не підключитесь до бази даних, у вас також буде глобальний спосіб доступу до пулу підключення до вашої бази даних, що призведе до помилки якщо у вас немає зв’язку.

// 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 для вас, якщо він є. Отримайте () завжди повертайте обіцянку. Так я керую своїм зв’язком, і він чудово працює. Це використання одинарного малюнка.
java-addict301

@ java-addict301 Хоча цей підхід забезпечує більш обтічний API, він має два недоліки. Перша полягає в тому, що не існує визначеного способу перевірки на наявність помилок підключення. Вам доведеться обробляти цю лінію в будь-якому місці, коли б вам не зателефонували. Мені подобається рано виходити з ладу з підключеннями до бази даних, і, як правило, я не дозволяю додатку завантажуватися із з'єднанням із базою даних. Інше питання - пропускна здатність. Оскільки у вас немає активного з'єднання, вам, можливо, доведеться почекати трохи довше під час першого дзвінка get (), над яким ви не матимете контролю. Не вдалося перекрити ваші показники звітування.
Стюарт

1
@Stewart Те, як я структурую додатки / послуги, - це зазвичай отримати конфігурацію з бази даних при запуску. Таким чином програма не запуститься, якщо база даних буде недоступною. Крім того, оскільки перший запит завжди під час запуску, з показниками цього дизайну не виникає проблем. Також виконайте повторне скидання виключення для з'єднання з одноразовим місцем в одиночному ключі з явною помилкою, яку він не може підключити. Оскільки програмі потрібно вловлювати помилки бази даних при використанні з'єднання, це не призводить до додаткових обробних вбудованих даних.
java-addict301

2
Привіт @Ayan. Важливо зазначити, що тут, коли ми дзвонимо, get()ми отримуємо пул з'єднань, а не єдине з'єднання. Пул підключень, як випливає з назви, - це логічна колекція підключень до бази даних. Якщо в пулі немає з'єднань, драйвер спробує відкрити його. Як тільки це з'єднання відкрите, воно використовується і повертається в пул. Наступного разу, коли доступ до пулу, це з'єднання може бути повторно використане. Приємним є те, що пул керує нашими з’єднаннями для нас, тож якщо зв’язок припинено, ми, можливо, ніколи не дізнаємось, як пул відкриє для нас новий.
Стюарт

11

Якщо у вас є Express.js, ви можете використовувати express-mongo-db для кешування та обміну з'єднанням MongoDB між запитами без пулу (оскільки прийнята відповідь говорить, що це правильний спосіб спільного доступу до з'єднання).

Якщо ні - ви можете переглянути його вихідний код і використовувати його в іншій рамці.


6

Я використовував generic-pool із підключеннями Redis у своєму додатку - настійно рекомендую. Його загальний, і я точно знаю, що він працює з mysql, тому я не думаю, що у вас будуть проблеми з монго

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


Mongo вже об'єднує з'єднання у драйвері, я, однак, відобразив мої з'єднання mongo в інтерфейс, який відповідає пулу вузлів, таким чином всі мої з'єднання відповідають одній схемі, навіть якщо у випадку з 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

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

Найкращим підходом до здійснення об’єднання з'єднань є створення однієї глобальної змінної масиву, яка містить ім'я db з об'єктом з'єднання, поверненим MongoClient, а потім повторно використовувати це з'єднання кожного разу, коли вам потрібно зв’язатися з базою даних.

  1. У вашому Server.js визначте var global.dbconnections = [];

  2. Створіть сервіс іменування з'єднанняService.js. У ньому будуть два методи 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;
} 

0

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

створити новий файл .env

CONNECTIONSTRING=x --> const client = new MongoClient(CONNECTIONSTRING) 
PORT=8080 
JWTSECRET=mysuper456secret123phrase

0

Якщо використовувати Express, існує ще один простий метод, який полягає у використанні вбудованої функції Express для обміну даними між маршрутами та модулями у вашій програмі. Існує об'єкт, який називається app.locals. Ми можемо приєднати до нього властивості та отримати доступ до нього зсередини наших маршрутів. Щоб скористатись цим, інстанціюйте своє з'єднання mongo у файлі 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і не потребує створення додаткових модулів.

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