Comment gérer les connexions MongoDB dans une application Web Node.js?


288

J'utilise le pilote natif de node-mongodb avec MongoDB pour écrire un site Web.

J'ai des questions sur la gestion des connexions:

  1. Est-ce suffisant d'utiliser une seule connexion MongoDB pour toutes les demandes? Y a-t-il des problèmes de performances? Sinon, puis-je configurer une connexion globale à utiliser dans l'ensemble de l'application?

  2. Si ce n'est pas le cas, est-ce bien d'ouvrir une nouvelle connexion lorsque la demande arrive et de la fermer lors du traitement de la demande? Est-il coûteux d'ouvrir et de fermer une connexion?

  3. Dois-je utiliser un pool de connexions global? J'entends que le pilote a un pool de connexion natif. est-ce un bon choix?

  4. Si j'utilise un pool de connexions, combien de connexions doivent être utilisées?

  5. Y a-t-il d'autres choses que je devrais remarquer?


@ IonicãBizãu, désolé, je n'ai pas utilisé nodejs depuis longtemps que je ne l'ai pas vu. Merci pour votre commentaire ~
Freewind

Réponses:


459

Le committer principal de node-mongodb-native dit :

Vous ouvrez do MongoClient.connect une fois lorsque votre application démarre et réutilise l'objet db. Ce n'est pas un pool de connexions singleton chaque .connect crée un nouveau pool de connexions.

Donc, pour répondre directement à votre question, réutilisez l'objet db qui en résulteMongoClient.connect() . Cela vous donne la mise en commun et fournira une augmentation de vitesse notable par rapport à l'ouverture / fermeture des connexions à chaque action db.



4
Ceci est la bonne réponse. La réponse acceptée est très fausse car elle dit d'ouvrir un pool de connexions pour chaque demande, puis de le fermer après l'avoir fait. Architecture horrible.
Saransh Mohapatra

7
Ceci est une bonne réponse. Mon dieu, imaginez que je dois ouvrir et fermer chaque fois que je fais quelque chose, ce serait 350K par heure juste pour mes inserts! C'est comme attaquer mon propre serveur.
Maziyar

1
@Cracker: Si vous avez une application express, vous pouvez enregistrer l'objet db req.dbavec ce middleware: github.com/floatdrop/express-mongo-db
floatdrop

1
s'il y a plusieurs bases de données .. dois-je ouvrir une connexion pour chaque base de données tous ensemble. Sinon, est-il OK d'ouvrir et de fermer au besoin?
Aman Gupta

45

Ouvrez une nouvelle connexion lorsque l'application Node.js démarre et réutilisez l' dbobjet de connexion existant :

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

Source: Comment ouvrir des connexions de base de données dans une application Node.js / Express


1
Cela crée une connexion à la base de données ... si vous souhaitez utiliser des pools, vous devez créer / fermer à chaque utilisation
amcdnl

15
Hors sujet, c'est le fichier NodeJS le plus étrange que j'aie jamais vu.
Hobbyist

1
Je n'ai jamais entendu parler de app.locals auparavant, mais je suis content que vous me les ayez présenté ici
Z_z_Z

1
M'a beaucoup aidé! Je fais l'erreur de créer / fermer une connexion DB pour chaque demande, la performance de mon application a chuté avec cela.
Leandro Lima,

18

Voici du code qui gérera vos connexions 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;

Lorsque vous démarrez le serveur, appelez initPool

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

Ensuite, dans tout autre module, vous pouvez effectuer les opérations suivantes:

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

Ceci est basé sur la documentation MongoDB . Jetez un coup d'oeil.


3
Mise à jour depuis 5.x: var option = {numberOfRetries: 5, auto_reconnect: true, poolSize: 40, connectTimeoutMS: 30000};
Blair

15

Gérez les pools de connexions mongo dans un seul module autonome. Cette approche présente deux avantages. Premièrement, il maintient votre code modulaire et plus facile à tester. Deuxièmement, vous n'êtes pas obligé de mélanger votre connexion à la base de données dans votre objet de demande qui n'est PAS l'endroit pour un objet de connexion à la base de données. (Étant donné la nature de JavaScript, je considérerais qu'il est très dangereux de mélanger quoi que ce soit à un objet construit par le code de bibliothèque). Donc, avec cela, il vous suffit de considérer un module qui exporte deux méthodes. connect = () => Promiseet get = () => dbConnectionObject.

Avec un tel module, vous pouvez d'abord vous connecter à la base de données

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

En vol, votre application peut simplement appeler get()lorsqu'elle a besoin d'une connexion DB.

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

Si vous configurez votre module db de la même manière que ci-dessous, vous aurez non seulement un moyen de vous assurer que votre application ne démarrera pas, sauf si vous disposez d'une connexion à la base de données, mais vous disposez également d'un moyen global d'accéder à votre pool de connexion à la base de données qui entraînera des erreurs si vous n'avez pas de connexion.

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

très utile, exactement ce que je cherchais!
agui

Mieux encore, vous pouvez simplement vous débarrasser de la fonction connect () et faire vérifier la fonction get () pour voir si la connexion est nulle et appeler connect pour vous si c'est le cas. Get () renvoie toujours une promesse. C'est ainsi que je gère ma connexion et cela fonctionne très bien. Il s'agit d'une utilisation du modèle singleton.
java-addict301

@ java-addict301 Bien que cette approche fournisse une API plus rationalisée, elle présente deux inconvénients. La première étant qu'il n'existe aucun moyen défini de vérifier les erreurs de connexion. Vous auriez à gérer cela en ligne partout quand vous appelez get. J'aime échouer tôt avec les connexions à la base de données et généralement je ne laisserai pas l'application démarrer sans connexion à la base de données. L'autre problème est le débit. Parce que vous n'avez pas de connexion active, vous devrez peut-être attendre un peu plus longtemps lors du premier appel get () sur lequel vous n'aurez aucun contrôle. Pourrait fausser vos statistiques de rapport.
Stewart

1
@Stewart La façon dont je structure les applications / services consiste généralement à récupérer une configuration de la base de données au démarrage. De cette façon, l'application ne pourra pas démarrer si la base de données est inaccessible. De plus, comme la première demande est toujours au démarrage, il n'y a aucun problème avec les mesures avec cette conception. Relancez également une exception de connexion avec en une seule place dans le singleton avec une erreur claire qu'il ne peut pas se connecter. Étant donné que l'application doit de toute façon détecter les erreurs de base de données lors de l'utilisation de la connexion, cela ne conduit pas à une gestion en ligne supplémentaire.
java-addict301

2
Salut @Ayan. Il est important de noter qu'ici, lorsque nous appelons, get()nous obtenons un pool de connexions et non une seule connexion. Un pool de connexions, comme son nom l'indique, est une collection logique de connexions à la base de données. S'il n'y a aucune connexion dans le pool, le pilote tentera d'en ouvrir une. Une fois cette connexion ouverte, elle est utilisée et renvoyée dans le pool. La prochaine fois que vous accéderez au pool, cette connexion pourra être réutilisée. La bonne chose ici est que la piscine gérera nos connexions pour nous, donc si une connexion est interrompue, nous ne le saurons peut-être jamais car la piscine en ouvrira une nouvelle pour nous.
Stewart

11

Si vous avez Express.js, vous pouvez utiliser express-mongo-db pour mettre en cache et partager la connexion MongoDB entre les demandes sans pool (car la réponse acceptée indique que c'est la bonne façon de partager la connexion).

Sinon, vous pouvez consulter son code source et l'utiliser dans un autre cadre.


6

J'ai utilisé un pool générique avec des connexions redis dans mon application - je le recommande vivement. Son générique et je sais certainement qu'il fonctionne avec mysql, donc je ne pense pas que vous aurez des problèmes avec cela et mongo

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


Mongo fait déjà le pool de connexions dans le pilote, j'ai cependant mappé mes connexions mongo dans une interface qui correspond au pool de nœuds, de cette façon toutes mes connexions suivent le même modèle, même si dans le cas de mongo, le nettoyage ne fonctionne pas déclencher quoi que ce soit.
Tracker1

4

Vous devez créer une connexion en tant que service, puis la réutiliser en cas de besoin.

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

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

et utilisez-le où vous voulez avec

import dbService from "db.service.js"
const db = dbService.db

1
Si mongo n'a pas pu se connecter, MongoClient.close () donne une erreur. Mais une bonne solution pour le problème d'origine.
Himanshu

3

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

Découvrez la source de Mongoose. Ils ouvrent une connexion et la lient à un objet Model. Ainsi, lorsque l'objet Model est requis, une connexion est établie avec la base de données. Le pilote s'occupe de la mise en commun des connexions.


Il s'agit du connecteur natif d' originemongodb .
CodeFinity

2

J'ai implémenté le code ci-dessous dans mon projet pour implémenter le regroupement de connexions dans mon code afin qu'il crée une connexion minimale dans mon projet et réutilise la connexion disponible

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

La meilleure approche pour implémenter le regroupement de connexions consiste à créer une variable de tableau global contenant le nom de la base de données avec l'objet de connexion renvoyé par MongoClient, puis à réutiliser cette connexion chaque fois que vous devez contacter Database.

  1. Dans votre Server.js, définissez var global.dbconnections = [];

  2. Créez un service nommant connectionService.js. Il aura 2 méthodes getConnection et createConnection. Ainsi, lorsque l'utilisateur appellera getConnection (), il trouvera des détails dans la variable de connexion globale et renverra les détails de connexion s'il existe déjà sinon il appellera createConnection () et renverra les détails de connexion.

  3. Appelez ce service en utilisant db_name et il renverra l'objet de connexion s'il en a déjà un autre, il créera une nouvelle connexion et vous le renverra.

J'espère que ça aide :)

Voici le code 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 -> nouveau projet -> nouveau cluster -> nouvelle collection -> connect -> adresse IP: 0.0.0.0/0 & db cred -> connectez votre application -> copiez la chaîne de connexion et collez le fichier .env de votre nœud et assurez-vous de remplacer "" par le mot de passe réel de l'utilisateur et de remplacer "/ test" par votre nom de base de données

créer un nouveau fichier .env

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

0

Si vous utilisez express, il existe une autre méthode plus simple, qui consiste à utiliser la fonction intégrée d'Express pour partager des données entre les routes et les modules de votre application. Il existe un objet appelé app.locals. Nous pouvons y attacher des propriétés et y accéder depuis l'intérieur de nos itinéraires. Pour l'utiliser, instanciez votre connexion mongo dans votre fichier 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'));

Cette connexion à la base de données, ou en fait toute autre donnée que vous souhaitez partager autour des modules de votre application, est désormais accessible dans vos itinéraires avec req.app.localscomme ci-dessous sans avoir besoin de créer et de nécessiter des modules supplémentaires.

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

Cette méthode garantit que vous avez une connexion à la base de données ouverte pour la durée de votre application, sauf si vous choisissez de la fermer à tout moment. Il est facilement accessible avec req.app.locals.your-collectionet ne nécessite pas la création de modules supplémentaires.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.