ฉันจะจัดการการเชื่อมต่อ MongoDB ในเว็บแอปพลิเคชัน Node.js ได้อย่างไร


288

ฉันใช้ไดรเวอร์node-mongodb-nativeกับ MongoDB เพื่อเขียนเว็บไซต์

ฉันมีคำถามบางอย่างเกี่ยวกับวิธีจัดการการเชื่อมต่อ:

  1. มีการใช้การเชื่อมต่อ MongoDB เพียงครั้งเดียวสำหรับคำขอทั้งหมดหรือไม่ มีปัญหาเรื่องประสิทธิภาพหรือไม่ ถ้าไม่ฉันสามารถตั้งค่าการเชื่อมต่อทั่วโลกเพื่อใช้ในแอปพลิเคชันทั้งหมดได้หรือไม่

  2. หากไม่เป็นเช่นนั้นจะดีหรือไม่ถ้าฉันเปิดการเชื่อมต่อใหม่เมื่อมีคำขอมาถึงและปิดเมื่อจัดการคำขอ การเปิดและปิดการเชื่อมต่อมีราคาแพงหรือไม่

  3. ฉันควรใช้พูลการเชื่อมต่อส่วนกลางหรือไม่ ฉันได้ยินว่าคนขับมีพูลการเชื่อมต่อดั้งเดิม มันเป็นทางเลือกที่ดีหรือไม่?

  4. หากฉันใช้พูลการเชื่อมต่อควรใช้การเชื่อมต่อจำนวนเท่าใด

  5. มีสิ่งอื่นใดอีกที่ฉันควรสังเกต


@ IonicãBizãuขอโทษฉันไม่ได้ใช้ nodejs เป็นเวลานานที่ฉันไม่ได้เห็น ขอบคุณสำหรับความคิดเห็นของคุณ ~
Freewind

คำตอบ:


459

committer หลักของ node-mongodb-native พูดว่า :

คุณเปิด do MongoClient.connect หนึ่งครั้งเมื่อแอปบู๊ตและนำวัตถุ db กลับมาใช้ใหม่ ไม่ใช่พูลการเชื่อมต่อแบบซิงเกิลแต่ละ. connect สร้างพูลการเชื่อมต่อใหม่

ดังนั้นเพื่อตอบคำถามของคุณโดยตรงนำมาใช้ใหม่วัตถุฐานข้อมูลที่เป็นผลมาจาก MongoClient.connect()สิ่งนี้จะช่วยให้คุณรวมกำไรและจะเพิ่มความเร็วที่เห็นได้ชัดเมื่อเปรียบเทียบกับการเปิด / ปิดการเชื่อมต่อในแต่ละการกระทำ db


3
เชื่อมโยงไปยัง MongoClient.connect () mongodb.github.io/node-mongodb-native/driver-articles/…
AndrewJM

4
นี่คือคำตอบที่ถูกต้อง คำตอบที่ยอมรับนั้นผิดมากเพราะมันบอกให้เปิดพูลการเชื่อมต่อสำหรับแต่ละคำขอแล้วปิดหลังจากทำเช่นนั้น สถาปัตยกรรมแย่มาก
Saransh Mohapatra

7
นี่คือคำตอบที่ถูก พระเจ้าของฉันจินตนาการว่าฉันต้องเปิดและปิดทุกครั้งที่ฉันทำบางสิ่งบางอย่างมันจะเป็น 350K ต่อชั่วโมงสำหรับเม็ดมีดของฉัน! มันเหมือนกับการโจมตีเซิร์ฟเวอร์ของฉันเอง
Maziyar

1
@Cracker: หากคุณมีแอปพลิเคชันแบบด่วนคุณสามารถบันทึกวัตถุ db req.dbด้วยมิดเดิลแวร์นี้: github.com/floatdrop/express-mongo-db
floatdrop

1
หากมีหลายฐานข้อมูล .. ฉันควรจะเปิดการเชื่อมต่อสำหรับแต่ละฐานข้อมูลทั้งหมดเข้าด้วยกัน ถ้าไม่สามารถเปิดและปิดได้เมื่อต้องการหรือไม่
Aman Gupta

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
ช่วยฉันมาก! ฉันทำผิดพลาดในการสร้าง / ปิดการเชื่อมต่อฐานข้อมูลสำหรับทุกคำขอความสมบูรณ์แบบของแอพของฉันลดลงด้วยสิ่งนี้
Leandro Lima

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 connection pool ในโมดูลเดียวที่มีอยู่ในตัวเอง วิธีการนี้ให้ประโยชน์สองประการ ประการแรกมันช่วยให้โค้ดของคุณเป็นโมดูลและทดสอบได้ง่ายขึ้น ประการที่สองคุณไม่ได้บังคับให้ผสมการเชื่อมต่อฐานข้อมูลของคุณในวัตถุคำขอของคุณซึ่งไม่ใช่ที่สำหรับวัตถุเชื่อมต่อฐานข้อมูล (ตามลักษณะของ JavaScript ฉันคิดว่ามันอันตรายอย่างยิ่งที่จะผสมสิ่งใด ๆ กับวัตถุที่สร้างโดยรหัสห้องสมุด) ดังนั้นโดยที่คุณจะต้องพิจารณาโมดูลที่ส่งออกสองวิธี และconnect = () => Promiseget = () => 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;
}

มีประโยชน์มากตรงกับที่ฉันต้องการ!
agui

ยังดีกว่าคุณสามารถกำจัดฟังก์ชั่นการเชื่อมต่อ () และตรวจสอบฟังก์ชั่น get () เพื่อดูว่าการเชื่อมต่อนั้นเป็นโมฆะหรือไม่และโทรหาคุณถ้าเป็น ได้รับ () คืนสัญญาเสมอ นี่คือวิธีที่ฉันจัดการการเชื่อมต่อและใช้งานได้ดี นี่คือการใช้รูปแบบซิงเกิล
java-addict301

@ java-addict301 ในขณะที่วิธีการนั้นให้ API ที่มีความคล่องตัวมากกว่า แต่ก็มีข้อเสียสองประการ เป็นคนแรกที่ไม่มีวิธีที่กำหนดไว้เพื่อตรวจสอบข้อผิดพลาดการเชื่อมต่อ คุณจะต้องจัดการกับอินไลน์นั้นทุกที่เมื่อคุณโทรหา ฉันชอบที่จะล้มเหลว แต่เนิ่นๆด้วยการเชื่อมต่อฐานข้อมูลและโดยทั่วไปฉันจะไม่ปล่อยให้แอปบู๊ตเชื่อมต่อกับฐานข้อมูล ปัญหาอื่นคือปริมาณงาน เนื่องจากคุณไม่มีการเชื่อมต่อที่ใช้งานอยู่คุณอาจต้องรออีกสักครู่สำหรับการรับสายแรก () ซึ่งคุณจะไม่สามารถควบคุมได้ ไม่สามารถวัดการรายงานของคุณได้
สจ๊วต

1
@Stewart วิธีที่ฉันจัดโครงสร้างแอปพลิเคชัน / บริการคือการดึงการกำหนดค่าจากฐานข้อมูลเมื่อเริ่มต้น ด้วยวิธีนี้แอปพลิเคชันจะไม่สามารถเริ่มต้นได้หากฐานข้อมูลไม่สามารถเข้าถึงได้ นอกจากนี้เนื่องจากคำขอแรกจะเปิดตลอดเวลาจึงไม่มีปัญหากับตัวชี้วัดที่มีการออกแบบนี้ นอกจากนี้ให้ทำข้อยกเว้นการเชื่อมต่อใหม่อีกครั้งด้วยการติดตั้งครั้งเดียวในข้อผิดพลาดที่ชัดเจนว่าไม่สามารถเชื่อมต่อได้ เนื่องจากแอปจำเป็นต้องตรวจจับข้อผิดพลาดของฐานข้อมูลเมื่อใช้การเชื่อมต่ออยู่สิ่งนี้จึงไม่นำไปสู่การจัดการแบบอินไลน์เพิ่มเติม
java-addict301

2
สวัสดี @ อยัน สิ่งสำคัญคือให้สังเกตว่าที่นี่เมื่อเราโทรหาget()เราจะได้รับพูลการเชื่อมต่อไม่ใช่การเชื่อมต่อเดียว พูลการเชื่อมต่อเนื่องจากชื่อหมายถึงเป็นชุดการเชื่อมต่อฐานข้อมูลแบบลอจิคัล หากไม่มีการเชื่อมต่อในพูลไดรเวอร์จะพยายามเปิดขึ้นมา เมื่อเปิดการเชื่อมต่อนั้นจะถูกใช้และกลับสู่พูล ครั้งต่อไปที่พูลเข้าถึงการเชื่อมต่อนี้อาจถูกนำมาใช้ซ้ำ สิ่งที่ดีที่นี่คือสระว่ายน้ำจะจัดการการเชื่อมต่อของเราสำหรับเราดังนั้นหากการเชื่อมต่อหลุดเราอาจไม่เคยรู้เพราะสระว่ายน้ำจะเปิดใหม่สำหรับเรา
สจ๊วต

11

หากคุณมี Express.js คุณสามารถใช้express-mongo-dbสำหรับการแคชและแบ่งปันการเชื่อมต่อ MongoDB ระหว่างการร้องขอโดยไม่มีกลุ่ม (เนื่องจากคำตอบที่ยอมรับบอกว่ามันเป็นวิธีที่เหมาะสมในการแบ่งปันการเชื่อมต่อ)

ถ้าไม่ใช่ - คุณสามารถดูซอร์สโค้ดของมันและใช้ในเฟรมเวิร์กอื่น


6

ฉันใช้สระว่ายน้ำทั่วไปพร้อมการเชื่อมต่อ Redis ในแอพของฉันฉันขอแนะนำ ทั่วไปและฉันรู้ว่ามันทำงานได้กับ mysql ดังนั้นฉันไม่คิดว่าคุณจะมีปัญหาใด ๆ กับมันและ mongo

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


Mongo ทำการเชื่อมต่อรวมในไดร์เวอร์อยู่แล้ว แต่แมปการเชื่อมต่อ Mongo ของฉันเข้ากับอินเตอร์เฟสที่ตรงกับ node-pool ด้วยวิธีนี้การเชื่อมต่อทั้งหมดของฉันเป็นไปตามรูปแบบเดียวกันแม้ว่าในกรณีของ 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
หาก Mongo ไม่สามารถเชื่อมต่อ MongoClient.close () จะแสดงข้อผิดพลาด แต่ทางออกที่ดีสำหรับปัญหาเดิม
Himanshu

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. สร้างการตั้งชื่อ ServiceService.js มันจะมี 2 วิธีคือ 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 & ฐานข้อมูลเครดิต -> เชื่อมต่อแอปพลิเคชันของคุณ -> คัดลอกสตริงการเชื่อมต่อและวางในไฟล์. 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และไม่จำเป็นต้องสร้างโมดูลเพิ่มเติมใด ๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.