Jak zarządzać połączeniami MongoDB w aplikacji internetowej Node.js?

288

Używam natywnego sterownika node-mongodb z MongoDB do napisania strony internetowej.

Mam kilka pytań na temat zarządzania połączeniami:

  1. Czy wystarczy użyć tylko jednego połączenia MongoDB dla wszystkich żądań? Czy są jakieś problemy z wydajnością? Jeśli nie, to czy mogę skonfigurować globalne połączenie do użycia w całej aplikacji?

  2. Jeśli nie, to czy dobrze jest otworzyć nowe połączenie, gdy nadejdzie żądanie, i zamknąć je, gdy zostanie obsłużone? Czy otwieranie i zamykanie połączenia jest drogie?

  3. Czy powinienem używać globalnej puli połączeń? Słyszałem, że sterownik ma rodzimą pulę połączeń. Czy to dobry wybór?

  4. Jeśli korzystam z puli połączeń, ile połączeń należy użyć?

  5. Czy są jeszcze inne rzeczy, które powinienem zauważyć?

Freewind
źródło
@ IonicãBizãu, przepraszam, nie używałem nodejs przez długi czas, że go nie widziałem. Dzięki za komentarz ~
Freewind

Odpowiedzi:

459

Główny committer do node-mongodb-native mówi :

Otwórz do MongoClient.connect raz, gdy aplikacja się uruchomi i ponownie wykorzystaj obiekt db. Nie jest to pojedyncza pula połączeń, każda .connect tworzy nową pulę połączeń.

Tak więc, aby bezpośrednio odpowiedzieć na twoje pytanie, ponownie wykorzystaj wynikowy obiekt dbMongoClient.connect() . Zapewnia to tworzenie puli i zapewnia zauważalny wzrost prędkości w porównaniu z połączeniami otwierania / zamykania przy każdej akcji db.

Max
źródło
3
Link do MongoClient.connect () mongodb.github.io/node-mongodb-native/driver-articles/…
AndrewJM
4
To jest poprawna odpowiedź. Przyjęta odpowiedź jest bardzo błędna, ponieważ mówi o otwarciu puli połączeń dla każdego żądania, a następnie jej zamknięciu. Okropna architektura.
Saransh Mohapatra,
7
To dobra odpowiedź. Mój bóg wyobraź sobie, że muszę otwierać i zamykać za każdym razem, gdy coś robię, byłoby to 350 K na godzinę tylko dla moich wkładek! To jest jak atakowanie własnego serwera.
Maziyar
1
@Cracker: Jeśli masz aplikację ekspresową, możesz zapisać obiekt db w req.dbtym oprogramowaniu pośrednim: github.com/floatdrop/express-mongo-db
floatdrop
1
jeśli istnieje wiele baz danych .. czy powinienem otworzyć połączenie dla każdej bazy danych razem. Jeśli nie, to czy można otwierać i zamykać w razie potrzeby?
Aman Gupta,
45

Otwórz nowe połączenie po uruchomieniu aplikacji Node.js i ponownie użyj istniejącego dbobiektu połączenia:

/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;

Źródło: Jak otwierać połączenia z bazą danych w aplikacji Node.js / Express

Konstantin Tarkus
źródło
1
Spowoduje to utworzenie jednego połączenia z bazą danych ... jeśli chcesz korzystać z pul, musisz utworzyć / zamknąć każde użycie
amcdnl
15
Nie na temat, to najdziwniejszy plik NodeJS, jaki kiedykolwiek widziałem.
Hobbyści
1
Nigdy wcześniej nie słyszałem o app.locals, ale cieszę się, że mnie tutaj przedstawiłeś
Z_z_Z
1
Bardzo mi pomogło! Popełniam błąd, tworząc / zamykając połączenie DB dla każdego żądania, wydajność mojej aplikacji spadła wraz z tym.
Leandro Lima
18

Oto kod, który będzie zarządzał Twoimi połączeniami 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;

Po uruchomieniu serwera zadzwoń initPool

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

Następnie w dowolnym innym module możesz wykonać następujące czynności:

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

Jest to oparte na dokumentacji MongoDB . Spójrz na to.

Yaki Klein
źródło
3
Aktualizuj od 5.x: var Option = {numberOfRetries: 5, auto_reconnect: true, poolSize: 40, connectTimeoutMS: 30000};
Blair,
15

Zarządzaj pulami połączeń mongo w jednym samodzielnym module. Takie podejście zapewnia dwie korzyści. Po pierwsze sprawia, że ​​Twój kod jest modułowy i łatwiejszy do przetestowania. Po drugie, nie musisz mieszać połączenia z bazą danych w obiekcie żądania, który NIE jest miejscem dla obiektu połączenia z bazą danych. (Biorąc pod uwagę naturę JavaScript, uważam, że bardzo niebezpieczne jest mieszanie czegokolwiek z obiektem zbudowanym przez kod biblioteki). Dlatego wystarczy wziąć pod uwagę moduł, który eksportuje dwie metody. connect = () => Promisea get = () => dbConnectionObject.

Za pomocą takiego modułu możesz najpierw połączyć się z bazą danych

// 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);
    });

Podczas lotu aplikacja może po prostu zadzwonić, get()gdy potrzebuje połączenia DB.

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

Jeśli skonfigurujesz moduł db w ten sam sposób, co poniżej, nie tylko zapewnisz, że aplikacja nie uruchomi się, chyba że masz połączenie z bazą danych, ale masz także globalny sposób na dostęp do puli połączeń z bazą danych, która spowoduje błąd jeśli nie masz połączenia.

// 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;
}
Stewart
źródło
bardzo przydatne, dokładnie to, czego szukałem!
agui
Co więcej, możesz po prostu pozbyć się funkcji connect () i poprosić o sprawdzenie funkcji get (), aby sprawdzić, czy połączenie jest zerowe, a jeśli tak, to wywołaj połączenie. Uzyskaj () zawsze zwraca obietnicę. W ten sposób zarządzam moim połączeniem i działa świetnie. Jest to użycie wzorca singletonu.
java-addict301
@ java-addict301 Chociaż takie podejście zapewnia bardziej usprawniony interfejs API, ma dwie wady. Po pierwsze, nie ma określonego sposobu sprawdzania błędów połączenia. Będziesz musiał poradzić sobie z tym wbudowanym wszędzie, kiedy tylko zadzwonisz. Lubię wcześnie zawodzić przy połączeniach z bazą danych i generalnie nie pozwalam na uruchomienie aplikacji bez połączenia z bazą danych. Innym problemem jest przepustowość. Ponieważ nie masz aktywnego połączenia, być może będziesz musiał poczekać trochę dłużej na pierwsze wywołanie get (), nad którym nie będziesz mieć kontroli. Może wypaczyć dane raportowania.
Stewart,
1
@Stewart Struktura aplikacji / usług polega na typowym pobieraniu konfiguracji z bazy danych podczas uruchamiania. W ten sposób aplikacja nie uruchomi się, jeśli baza danych będzie niedostępna. Ponieważ pierwsze żądanie jest zawsze uruchamiane, nie ma problemu z danymi z tego projektu. Powtórz także wyjątek połączenia z jednym miejscem w singletonie z wyraźnym błędem, którego nie można połączyć. Ponieważ aplikacja i tak musi wychwytywać błędy bazy danych podczas korzystania z połączenia, nie prowadzi to do żadnej dodatkowej obsługi.
java-addict301
2
Cześć @Ayan. Należy zauważyć, że tutaj, gdy dzwonimy get(), otrzymujemy pulę połączeń, a nie pojedyncze połączenie. Pula połączeń, jak sama nazwa wskazuje, jest logicznym zbiorem połączeń z bazą danych. Jeśli w puli nie ma połączeń, sterownik spróbuje je otworzyć. Po otwarciu tego połączenia zostanie ono użyte i zwrócone do puli. Przy następnym dostępie do puli połączenie to może zostać ponownie wykorzystane. Fajną rzeczą jest to, że pula zarządza dla nas naszymi połączeniami, więc jeśli połączenie zostanie zerwane, możemy się nigdy nie dowiedzieć, ponieważ pula otworzy dla nas nowe.
Stewart
11

Jeśli masz Express.js, możesz użyć express-mongo-db do buforowania i udostępniania połączenia MongoDB między żądaniami bez puli (ponieważ zaakceptowana odpowiedź mówi, że jest to właściwy sposób udostępniania połączenia).

Jeśli nie - możesz spojrzeć na jego kod źródłowy i użyć go w innym środowisku.

floatdrop
źródło
6

Korzystam z generycznej puli z połączeniami redis w mojej aplikacji - bardzo ją polecam. Jest ogólny i zdecydowanie wiem, że działa z mysql, więc nie sądzę, abyś miał z tym jakieś problemy i mongo

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

ControlAltDel
źródło
Mongo już tworzy pulę połączeń w sterowniku, jednak zamapowałem moje połączenia mongo na interfejs zgodny z pulą węzłów, w ten sposób wszystkie moje połączenia mają ten sam wzór, chociaż w przypadku mongo czyszczenie nie właściwie wyzwalam wszystko.
Tracker1
4

Powinieneś utworzyć połączenie jako usługę, a następnie użyć go w razie potrzeby.

// 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;

moja próbka 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}`);
  });
});

i używaj go gdziekolwiek chcesz

import dbService from "db.service.js"
const db = dbService.db
TheAlemazing
źródło
1
Jeśli mongo nie może się połączyć, MongoClient.close () daje błąd. Ale dobre rozwiązanie oryginalnego problemu.
Himanshu,
2

W moim projekcie zaimplementowałem poniższy kod, aby zaimplementować pule połączeń w moim kodzie, aby utworzył minimalne połączenie w moim projekcie i ponownie używał dostępnego połączenia

/* 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");

});
Aditya Parmar
źródło
1

Najlepszym podejściem do wdrożenia puli połączeń jest utworzenie jednej globalnej zmiennej tablicowej, która przechowuje nazwę db z obiektem połączenia zwróconym przez MongoClient, a następnie ponownie wykorzystuje to połączenie, ilekroć trzeba skontaktować się z bazą danych.

  1. W swojej Server.js zdefiniuj var global.dbconnections = [];

  2. Utwórz połączenie usługi ServiceService.js. Będzie miał 2 metody getConnection i createConnection. Kiedy więc użytkownik wywoła getConnection (), znajdzie szczegóły w globalnej zmiennej połączenia i zwróci szczegóły połączenia, jeśli już istnieje, wywoła metodę createConnection () i zwróci szczegóły połączenia.

  3. Wywołaj tę usługę, używając nazwy_db, a zwróci obiekt połączenia, jeśli już ma, utworzy nowe połączenie i zwróci go.

Mam nadzieję, że to pomoże :)

Oto kod 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
źródło
0

mongodb.com -> nowy projekt -> nowy klaster -> nowa kolekcja -> połącz -> adres IP: 0.0.0.0/0 i db cred -> połącz swoją aplikację -> skopiuj parametry połączenia i wklej do pliku .env swojego węzła aplikacji i pamiętaj, aby zastąpić „” rzeczywistym hasłem użytkownika, a także „/ test” nazwą db

utwórz nowy plik .env

CONNECTIONSTRING=x --> const client = new MongoClient(CONNECTIONSTRING) 
PORT=8080 
JWTSECRET=mysuper456secret123phrase
anoNewb
źródło
0

Jeśli używasz ekspresu, istnieje inna prostsza metoda, która polega na wykorzystaniu wbudowanej funkcji Express do udostępniania danych między trasami i modułami w aplikacji. Istnieje obiekt o nazwie app.locals. Możemy dołączać do niego właściwości i uzyskiwać do nich dostęp z naszych tras. Aby z niego skorzystać, utwórz połączenie mongo w pliku 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'));

To połączenie z bazą danych, a nawet wszelkie inne dane, które chcesz udostępnić wokół modułów Twojej aplikacji, są teraz dostępne na twoich trasach, req.app.localsjak poniżej, bez potrzeby tworzenia i wymagania dodatkowych modułów.

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));
});

Ta metoda zapewnia, że ​​połączenie z bazą danych jest otwarte na czas trwania aplikacji, chyba że zdecydujesz się ją zamknąć w dowolnym momencie. Jest łatwo dostępny req.app.locals.your-collectioni nie wymaga tworzenia żadnych dodatkowych modułów.

Hoppo
źródło