Wie verwalte ich MongoDB-Verbindungen in einer Node.js-Webanwendung?

288

Ich verwende den Node-Mongodb-nativen Treiber mit MongoDB, um eine Website zu schreiben.

Ich habe einige Fragen zum Verwalten von Verbindungen:

  1. Reicht es aus, nur eine MongoDB-Verbindung für alle Anfragen zu verwenden? Gibt es Leistungsprobleme? Wenn nicht, kann ich eine globale Verbindung für die gesamte Anwendung einrichten?

  2. Wenn nicht, ist es gut, wenn ich bei Eingang der Anfrage eine neue Verbindung öffne und sie bei Bearbeitung der Anfrage schließe? Ist es teuer, eine Verbindung zu öffnen und zu schließen?

  3. Soll ich einen globalen Verbindungspool verwenden? Ich habe gehört, der Treiber hat einen nativen Verbindungspool. Ist es eine gute Wahl?

  4. Wenn ich einen Verbindungspool verwende, wie viele Verbindungen sollten verwendet werden?

  5. Gibt es andere Dinge, die ich beachten sollte?

Freilauf
quelle
@ IonicãBizãu, sorry, ich habe nodejs schon lange nicht mehr benutzt, ich habe es nicht gesehen. Vielen Dank für Ihren Kommentar ~
Freewind

Antworten:

459

Der primäre Committer für Node-Mongodb-Native sagt :

Sie öffnen do MongoClient.connect einmal, wenn Ihre App startet und das Datenbankobjekt wiederverwendet. Es ist kein Singleton-Verbindungspool. Jeder .connect erstellt einen neuen Verbindungspool.

Um Ihre Frage direkt zu beantworten, verwenden Sie das daraus resultierende Datenbankobjekt erneutMongoClient.connect() . Dies gibt Ihnen ein Pooling und eine spürbare Geschwindigkeitssteigerung im Vergleich zum Öffnen / Schließen von Verbindungen bei jeder Datenbankaktion.

Max
quelle
3
Link zu MongoClient.connect () mongodb.github.io/node-mongodb-native/driver-articles/…
AndrewJM
4
Dies ist die richtige Antwort. Die akzeptierte Antwort ist sehr falsch, da sie besagt, dass für jede Anforderung ein Verbindungspool geöffnet und anschließend geschlossen werden soll. Schreckliche Architektur.
Saransh Mohapatra
7
Dies ist eine richtige Antwort. Mein Gott stellt sich vor, dass ich jedes Mal, wenn ich etwas mache, öffnen und schließen muss, es wären 350K pro Stunde, nur für meine Beilagen! Es ist, als würde ich meinen eigenen Server angreifen.
Maziyar
1
@Cracker: Wenn Sie Express-Anwendung haben, können Sie DB-Objekt req.dbmit dieser Middleware speichern : github.com/floatdrop/express-mongo-db
floatdrop
1
Wenn es mehrere Datenbanken gibt, sollte ich eine Verbindung für jede Datenbank insgesamt öffnen. Wenn nicht, ist es in Ordnung, bei Bedarf zu öffnen und zu schließen?
Aman Gupta
45

Öffnen Sie beim Starten der Anwendung Node.js eine neue Verbindung und verwenden Sie das vorhandene dbVerbindungsobjekt erneut:

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

Quelle: So öffnen Sie Datenbankverbindungen in einer Node.js / Express-App

Konstantin Tarkus
quelle
1
Dies schafft eine Datenbankverbindung ... Wenn Sie Pools verwenden möchten, müssen Sie bei jeder Verwendung erstellen / schließen
amcdnl
15
Off-Topic ist dies die seltsamste NodeJS-Datei, die ich je gesehen habe.
Hobbyist
1
Ich habe noch nie von app.locals gehört, aber ich bin froh, dass Sie mich hier vorgestellt haben
Z_z_Z
1
Hat mir viel geholfen! Ich habe den Fehler gemacht, für jede Anfrage eine DB-Verbindung zu erstellen / zu schließen. Die Leistung meiner App ist damit gesunken.
Leandro Lima
18

Hier ist ein Code, der Ihre MongoDB-Verbindungen verwaltet.

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;

Wenn Sie den Server starten, rufen Sie an initPool

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

In jedem anderen Modul können Sie dann Folgendes tun:

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

Dies basiert auf der MongoDB-Dokumentation . Schau es dir an.

Yaki Klein
quelle
3
Update seit 5.x: var option = {numberOfRetries: 5, auto_reconnect: true, poolSize: 40, connectTimeoutMS: 30000};
Blair
15

Verwalten Sie Mongo-Verbindungspools in einem eigenständigen Modul. Dieser Ansatz bietet zwei Vorteile. Erstens bleibt Ihr Code modular und einfacher zu testen. Zweitens müssen Sie Ihre Datenbankverbindung nicht in Ihrem Anforderungsobjekt verwechseln, das NICHT der Ort für ein Datenbankverbindungsobjekt ist. (Angesichts der Natur von JavaScript würde ich es als sehr gefährlich betrachten, irgendetwas in ein Objekt zu mischen, das aus Bibliothekscode besteht). Damit müssen Sie nur ein Modul berücksichtigen, das zwei Methoden exportiert. connect = () => Promiseund get = () => dbConnectionObject.

Mit einem solchen Modul können Sie zunächst eine Verbindung zur Datenbank herstellen

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

Während des Fluges kann Ihre App einfach anrufen, get()wenn sie eine DB-Verbindung benötigt.

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

Wenn Sie Ihr Datenbankmodul auf die gleiche Weise wie folgt einrichten, können Sie nicht nur sicherstellen, dass Ihre Anwendung nur dann gestartet wird, wenn Sie über eine Datenbankverbindung verfügen, sondern auch global auf Ihren fehlerhaften Datenbankverbindungspool zugreifen wenn Sie keine Verbindung haben.

// 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
quelle
sehr nützlich, genau das, wonach ich gesucht habe!
Agui
Besser noch, Sie könnten einfach die Funktion connect () entfernen und die Funktion get () überprüfen lassen, um festzustellen, ob die Verbindung null ist, und connect für Sie aufrufen, wenn dies der Fall ist. Habe get () immer ein Versprechen zurückgegeben. So verwalte ich meine Verbindung und es funktioniert großartig. Dies ist eine Verwendung des Singleton-Musters.
Java-Addict301
@ java-addict301 Dieser Ansatz bietet zwar eine optimierte API, weist jedoch zwei Nachteile auf. Das erste ist, dass es keine definierte Möglichkeit gibt, nach Verbindungsfehlern zu suchen. Sie müssten diese Inline überall erledigen, wann immer Sie get anrufen. Ich mag es, frühzeitig mit Datenbankverbindungen zu scheitern, und im Allgemeinen lasse ich die App nicht ohne Verbindung zur Datenbank starten. Das andere Problem ist der Durchsatz. Da Sie keine aktive Verbindung haben, müssen Sie beim ersten Aufruf von get (), über den Sie keine Kontrolle haben, möglicherweise etwas länger warten. Könnte Ihre Berichtsmetriken verzerren.
Stewart
1
@Stewart Die Art und Weise, wie ich Anwendungen / Dienste strukturiere, besteht darin, beim Start normalerweise eine Konfiguration aus der Datenbank abzurufen. Auf diese Weise kann die Anwendung nicht gestartet werden, wenn auf die Datenbank nicht zugegriffen werden kann. Da die erste Anforderung immer beim Start erfolgt, gibt es bei diesem Entwurf keine Probleme mit den Metriken. Führen Sie außerdem eine Verbindungsausnahme mit an einer Stelle im Singleton erneut aus, mit einem eindeutigen Fehler, dass keine Verbindung hergestellt werden kann. Da die App bei Verwendung der Verbindung ohnehin Datenbankfehler abfangen muss, führt dies zu keiner zusätzlichen Inline-Behandlung.
Java-Addict301
2
Hallo @Ayan. Es ist wichtig zu beachten, dass wir hier get()einen Verbindungspool erhalten, nicht eine einzelne Verbindung , wenn wir anrufen . Ein Verbindungspool ist, wie der Name schon sagt, eine logische Sammlung von Datenbankverbindungen. Wenn im Pool keine Verbindungen vorhanden sind, versucht der Treiber, eine zu öffnen. Sobald diese Verbindung geöffnet ist, wird sie verwendet und an den Pool zurückgegeben. Beim nächsten Zugriff auf den Pool wird diese Verbindung möglicherweise wiederverwendet. Das Schöne hier ist, dass der Pool unsere Verbindungen für uns verwaltet. Wenn also eine Verbindung getrennt wird, wissen wir möglicherweise nie, dass der Pool eine neue für uns öffnet.
Stewart
11

Wenn Sie über Express.js verfügen, können Sie express-mongo-db zum Zwischenspeichern und Freigeben der MongoDB-Verbindung zwischen Anforderungen ohne Pool verwenden (da die akzeptierte Antwort besagt, dass dies der richtige Weg ist, die Verbindung freizugeben).

Wenn nicht, können Sie sich den Quellcode ansehen und ihn in einem anderen Framework verwenden.

floatdrop
quelle
6

Ich habe Generic-Pool mit Redis-Verbindungen in meiner App verwendet - ich kann es nur empfehlen. Es ist generisch und ich weiß definitiv, dass es mit MySQL funktioniert, also glaube ich nicht, dass Sie Probleme damit und mit Mongo haben werden

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

ControlAltDel
quelle
Mongo führt bereits Verbindungspooling im Treiber durch. Ich habe jedoch meine Mongo-Verbindungen einer Schnittstelle zugeordnet, die dem Knotenpool entspricht. Auf diese Weise folgen alle meine Verbindungen demselben Muster, obwohl dies bei Mongo nicht der Fall ist eigentlich alles auslösen.
Tracker1
4

Sie sollten eine Verbindung als Dienst erstellen und bei Bedarf wiederverwenden.

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

mein App.js Beispiel

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

und verwenden Sie es, wo immer Sie wollen

import dbService from "db.service.js"
const db = dbService.db
TheAlemazing
quelle
1
Wenn Mongo keine Verbindung herstellen konnte, gibt MongoClient.close () einen Fehler aus. Aber eine gute Lösung für das ursprüngliche Problem.
Himanshu
3

http://mongoosejs.com/docs/api.html

Überprüfen Sie die Quelle des Mungos. Sie öffnen eine Verbindung und binden sie an ein Modellobjekt. Wenn das Modellobjekt benötigt wird, wird eine Verbindung zur Datenbank hergestellt. Der Treiber kümmert sich um das Verbindungspooling.

srquinn
quelle
Original ist für nativen mongodb Anschluss.
CodeFinity
2

Ich habe den folgenden Code in meinem Projekt implementiert, um das Verbindungspooling in meinem Code zu implementieren, damit eine Mindestverbindung in meinem Projekt erstellt und die verfügbare Verbindung wiederverwendet wird

/* 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
quelle
1

Der beste Ansatz zum Implementieren des Verbindungspoolings besteht darin, eine globale Arrayvariable zu erstellen, die den Datenbanknamen mit dem von MongoClient zurückgegebenen Verbindungsobjekt enthält, und diese Verbindung dann wiederzuverwenden, wenn Sie Kontakt mit Database aufnehmen müssen.

  1. Definieren Sie in Ihren Server.js var global.dbconnections = [];

  2. Erstellen Sie einen Dienst mit dem Namen connectionService.js. Es gibt zwei Methoden: getConnection und createConnection. Wenn der Benutzer getConnection () aufruft, findet er Details in der globalen Verbindungsvariablen und gibt Verbindungsdetails zurück, falls bereits vorhanden. Andernfalls ruft er createConnection () auf und gibt Verbindungsdetails zurück.

  3. Rufen Sie diesen Dienst mit db_name auf und er gibt ein Verbindungsobjekt zurück, wenn es bereits ein anderes hat. Es erstellt eine neue Verbindung und gibt sie an Sie zurück.

Ich hoffe es hilft :)

Hier ist der Code von 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
quelle
0

mongodb.com -> neues Projekt -> neuer Cluster -> neue Sammlung -> verbinden -> IP-Adresse: 0.0.0.0/0 & db cred -> verbinden Sie Ihre Anwendung -> kopieren Sie die Verbindungszeichenfolge und fügen Sie sie in die .env-Datei Ihres Knotens ein App und stellen Sie sicher, dass Sie "" durch das tatsächliche Passwort für den Benutzer ersetzen und "/ test" durch Ihren Datenbanknamen ersetzen

neue Datei erstellen .env

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

Wenn Sie Express verwenden, gibt es eine andere einfachere Methode: Verwenden Sie die integrierte Express-Funktion, um Daten zwischen Routen und Modulen in Ihrer App auszutauschen. Es gibt ein Objekt namens app.locals. Wir können ihm Eigenschaften hinzufügen und von unseren Routen aus darauf zugreifen. Um es zu verwenden, instanziieren Sie Ihre Mongo-Verbindung in Ihrer app.js-Datei.

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

Auf diese Datenbankverbindung oder andere Daten, die Sie für die Module Ihrer App freigeben möchten, kann jetzt innerhalb Ihrer Routen req.app.localswie folgt zugegriffen werden, ohne dass zusätzliche Module erstellt und benötigt werden müssen.

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

Diese Methode stellt sicher, dass für die Dauer Ihrer App eine Datenbankverbindung geöffnet ist, es sei denn, Sie möchten sie jederzeit schließen. Es ist leicht zugänglich req.app.locals.your-collectionund erfordert keine Erstellung zusätzlicher Module.

Hoppo
quelle