Bir Node.js web uygulamasında MongoDB bağlantılarını nasıl yönetirim?


288

Bir web sitesi yazmak için MongoDB ile node-mongodb-native sürücüsünü kullanıyorum .

Bağlantıları yönetme hakkında bazı sorularım var:

  1. Tüm istekler için tek bir MongoDB bağlantısı kullanmak yeterli mi? Performans sorunu var mı? Değilse, tüm uygulamada kullanmak için global bir bağlantı kurabilir miyim?

  2. Değilse, istek geldiğinde yeni bir bağlantı açarsam ve istek işlendiğinde kapatılırsa iyi olur mu? Bağlantı açmak ve kapatmak pahalı mı?

  3. Genel bağlantı havuzu kullanmalı mıyım? Sürücünün yerel bir bağlantı havuzu olduğunu duydum. İyi bir seçim mi?

  4. Bir bağlantı havuzu kullanırsam, kaç bağlantı kullanılmalıdır?

  5. Dikkat etmem gereken başka şeyler var mı?


@ IonicãBizãu, özür dilerim, uzun süredir nodejs kullanmadım.
Yorumunuz

Yanıtlar:


459

Düğüm-mongodb-native'e birincil geçiş :

Uygulamanız önyüklendiğinde ve db nesnesini yeniden kullandığında do MongoClient.connect öğesini bir kez açarsınız. Bu tek bir bağlantı havuzu değil .connect yeni bir bağlantı havuzu oluşturur.

Bu nedenle, sorunuzu doğrudan yanıtlamak için, ortaya çıkan db nesnesini yeniden kullanınMongoClient.connect() . Bu size havuz verir ve her db eylemindeki açılış / kapanış bağlantılarına kıyasla fark edilir bir hız artışı sağlar.



4
Bu doğru cevap. Kabul edilen cevap, her istek için bir bağlantı havuzu açıp bunu yaptıktan sonra kapatmayı söylediği için çok yanlıştır. Korkunç mimari.
Saransh Mohapatra

7
Bu doğru bir cevap. Tanrım, her eklediğimde sadece eklerim için saatte 350K olacağını açmak ve kapatmak zorunda olduğumu hayal edin! Kendi sunucuma saldırmak gibi.
Maziyar

1
@Cracker: Ekspres uygulamanız varsa, db nesnesini req.dbbu ara yazılımla kaydedebilirsiniz : github.com/floatdrop/express-mongo-db
floatdrop

1
birden fazla veritabanı varsa .. hep birlikte her veritabanı için bir bağlantı açmak gerekir. Değilse, gerektiğinde açılıp kapanmak uygun mudur?
Aman Gupta

45

Node.js uygulaması başladığında yeni bir bağlantı açın ve mevcut dbbağlantı nesnesini yeniden kullanın :

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

Kaynak: Node.js / Express Uygulamasında Veritabanı Bağlantıları Nasıl Açılır


1
Bu bir veritabanı bağlantısı oluşturur ... havuzları kullanmak istiyorsanız, her kullanımda oluşturmak / kapatmak zorunda
amcdnl

15
Konu dışı, bu şimdiye kadar gördüğüm en garip NodeJS dosyası.
Hobi uzmanı

1
Daha önce app.locals duymadım, ama beni burada onları tanıttı sevindim
Z_z_Z

1
Bana çok yardım etti! Ben her istek için DB bağlantısı oluşturmak / kapatmak için hata yapmak, benim app performans bu düştü.
Leandro Lima

18

İşte MongoDB bağlantılarınızı yönetecek bazı kodlar.

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;

Sunucuyu başlattığınızda, initPool

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

Daha sonra başka bir modülde aşağıdakileri yapabilirsiniz:

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

Bu MongoDB belgelerine dayanmaktadır . Ona bir göz atın.


3
5.x'den beri güncelle: var option = {numberOfRetries: 5, auto_reconnect: true, poolSize: 40, connectTimeoutMS: 30000};
Blair

15

Tek bir bağımsız modülde mongo bağlantı havuzlarını yönetin. Bu yaklaşım iki fayda sağlar. Öncelikle kodunuzu modüler ve test etmeyi kolaylaştırır. İkincisi, veritabanı bağlantınızı bir veritabanı bağlantı nesnesinin yeri DEĞİL olan istek nesnenizde karıştırmak zorunda değilsiniz. (JavaScript'in doğası göz önüne alındığında, kütüphane koduyla oluşturulan bir nesneye herhangi bir şey karıştırmanın çok tehlikeli olduğunu düşünürüm). Bu nedenle, sadece iki yöntemi ihraç eden bir modülü düşünmeniz gerekir. connect = () => Promiseve get = () => dbConnectionObject.

Böyle bir modül ile önce veritabanına bağlanabilirsiniz

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

Uçuş get()sırasında uygulamanız bir DB bağlantısına ihtiyaç duyduğunda arayabilir .

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

Eğer db modülünüzü aşağıdaki gibi aynı şekilde kurarsanız, sadece bir veritabanı bağlantınız yoksa uygulamanızın önyükleme yapmamasını sağlamak için bir yolunuz olmayacaktır. Eğer bir bağlantınız yoksa.

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

çok yararlı, tam olarak aradığım şey!
agui

Daha da iyisi, sadece connect () fonksiyonundan kurtulabilir ve get () fonksiyonunun bağlantının boş olup olmadığını kontrol etmesini ve eğer sizin için connect'i çağırmasını sağlayabilirsiniz. Get () her zaman bir söz ver. Bağlantımı bu şekilde yönetiyorum ve harika çalışıyor. Bu, singleton deseninin bir kullanımıdır.
java-addict301

@ java-addict301 Bu yaklaşım daha akıcı bir API sağlarken iki dezavantajı vardır. Birincisi, bağlantı hatalarını kontrol etmek için tanımlanmış bir yol olmamasıdır. Get dediğinde her yerde bu satır içi işlemek zorunda kalacaksın. Ben veritabanı bağlantıları ile erken başarısız olmak istiyorum ve genellikle app veritabanına bir bağlantı ile önyükleme izin vermez. Diğer mesele iş hacmidir. Etkin bir bağlantınız olmadığından, kontrol sahibi olamayacağınız ilk get () çağrısında biraz daha beklemeniz gerekebilir. Raporlama metriklerinizi eğriltebilir.
Stewart

1
@Stewart Uygulamaları / hizmetleri yapılandırma yöntemim genellikle başlangıçta veritabanından bir yapılandırma almaktır. Bu şekilde, veritabanına erişilemiyorsa uygulama başlatılamaz. Ayrıca, ilk istek her zaman başlangıçta olduğundan, bu tasarımdaki metriklerle ilgili bir sorun yoktur. Ayrıca, bağlantı kuramadığı tek bir yerde bir bağlantı istisnasını yeniden bağlayamaz. Uygulamanın bağlantıyı yine de kullanırken veritabanı hatalarını yakalaması gerektiğinden, bu ekstra satır içi işlemeye yol açmaz.
java-addict301

2
Merhaba @ Ali. Burada aradığımızda get()tek bir bağlantı değil bir bağlantı havuzu elde ettiğimizi belirtmek önemlidir . Bir bağlantı havuzu, adından da anlaşılacağı gibi, veritabanı bağlantılarının mantıklı bir koleksiyonudur. Havuzda bağlantı yoksa, sürücü bir tane açmaya çalışır. Bu bağlantı açıldıktan sonra kullanılır ve havuza geri döner. Havuz bir sonraki erişiminizde bu bağlantı yeniden kullanılabilir. Burada güzel bir şey havuz bizim için bağlantılarımızı yönetecektir, bu yüzden bir bağlantı koparsa havuz bizim için yeni bir tane açacağımız gibi asla bilemeyebiliriz.
Stewart

11

Express.js'niz varsa , MongoDB bağlantısını havuzsuz istekler arasında önbelleğe almak ve paylaşmak için express-mongo-db'yi kullanabilirsiniz (kabul edilen cevap bağlantıyı paylaşmanın doğru yolu olduğunu söylediğinden).

Değilse - kaynak koduna bakabilir ve başka bir çerçevede kullanabilirsiniz.


6

Benim app redis bağlantıları ile genel havuz kullanıyorum - kesinlikle tavsiye ederim. Onun jenerik ve kesinlikle mysql ile çalıştığını biliyorum, bu yüzden onunla ve mongo ile herhangi bir sorun olacağını sanmıyorum

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


Mongo zaten sürücüde bağlantı havuzu oluşturuyor, ancak mongo bağlantılarımı düğüm havuzuyla eşleşen bir arayüze eşledim, bu şekilde tüm bağlantılarm aynı modeli takip ediyor, mongo durumunda temizleme yok aslında bir şey tetikler.
Tracker1

4

Hizmet olarak bir bağlantı oluşturmalı ve sonra gerektiğinde yeniden kullanmalısınız.

// 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 örneğim

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

ve istediğiniz yerde kullanın

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

1
Mongo bağlanamazsa, MongoClient.close () bir hata verir. Ancak orijinal sorun için iyi bir çözüm.
Himanshu

2

Projemde minimum bağlantı oluşturacak ve kullanılabilir bağlantıyı yeniden kullanacak şekilde kodumda bağlantı havuzu oluşturmak için projemde aşağıdaki kodu uyguladım

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

Bağlantı havuzu oluşturmak için en iyi yaklaşım, MongoClient tarafından döndürülen bağlantı nesnesiyle db adını tutan bir genel dizi değişkeni oluşturmanız ve sonra Veritabanına başvurmanız gerektiğinde bu bağlantıyı yeniden kullanmanızdır.

  1. Server.js dosyasında var global.dbconnections = [];

  2. ConnectionService.js adlı bir Hizmet adlandırma oluşturun. GetConnection ve createConnection olmak üzere 2 yöntemi olacaktır. Kullanıcı getConnection () öğesini çağırdığında, genel bağlantı değişkeninde ayrıntı bulur ve zaten varsa bağlantı ayrıntılarını döndürür, createConnection () öğesini çağırır ve bağlantı Ayrıntılarını döndürür.

  3. Bu hizmeti db_name kullanarak çağırın ve zaten varsa yeni bir bağlantı oluşturacak ve size geri gönderecek bağlantı nesnesini döndürecektir.

Umarım yardımcı olur :)

ConnectionService.js kodu şöyledir:

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 -> yeni proje -> yeni küme -> yeni koleksiyon -> bağlan -> IP adresi: 0.0.0.0/0 & db cred -> uygulamanızı bağlayın -> bağlantı dizesini kopyalayın ve düğümünüzün .env dosyasını yapıştırın uygulamasını kullanın ve "" kullanıcı için gerçek şifreyle değiştirdiğinizden ve "/ test" i db adınızla değiştirdiğinizden emin olun.

yeni dosya oluştur.

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

0

Express kullanıyorsanız, uygulamanızdaki rotalar ve modüller arasında veri paylaşmak için Express'in yerleşik özelliğinden yararlanmak için daha basit bir yöntem daha vardır. App.locals adında bir nesne var. Ona özellikler ekleyebilir ve rotalarımızın içinden erişebiliriz. Bunu kullanmak için, app.js dosyanızda mongo bağlantınızı başlatın.

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

Bu veritabanı bağlantısına veya gerçekten de uygulamanızın modülleri etrafında paylaşmak istediğiniz diğer verilere artık req.app.localsek modüller oluşturmaya ve gerek kalmadan rotanızdan aşağıdaki gibi erişilebilir .

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

Bu yöntem, herhangi bir zamanda kapatmayı seçmediğiniz sürece uygulamanız süresince açık bir veritabanı bağlantınızın olmasını sağlar. Kolayca erişilebilir req.app.locals.your-collectionve herhangi bir ek modülün oluşturulmasını gerektirmez.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.