Как мне управлять соединениями MongoDB в веб-приложении Node.js.
Я использую драйвер node-mongodb-native с MongoDB для написания веб-сайта.
У меня есть несколько вопросов о том, как управлять соединениями:
Достаточно ли использовать только одно подключение MongoDB для всех запросов? Есть ли проблемы с производительностью? Если нет, могу ли я установить глобальное соединение для использования во всем приложении?
Если нет, то хорошо ли мне открывать новое соединение при получении запроса и закрывать его при обработке запроса? Это дорого, чтобы открыть и закрыть соединение?
Должен ли я использовать глобальный пул соединений? Я слышал, что драйвер имеет собственный пул соединений. Это хороший выбор?
Если я использую пул соединений, сколько соединений нужно использовать?
Есть ли другие вещи, на которые я должен обратить внимание?
15 ответов
Основной коммиттер для node-mongodb-native говорит:
Вы открываете do MongoClient.connect один раз, когда ваше приложение загружается, и повторно используете объект db. Это не пул одноэлементных соединений, каждый.connect создает новый пул соединений.
Итак, чтобы ответить на ваш вопрос напрямую, повторно используйте объект db, полученный из MongoClient.connect(). Это дает вам пул и обеспечит заметное увеличение скорости по сравнению с открытием / закрытием соединений при каждом действии БД.
Откройте новое соединение при запуске приложения 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
Вот код, который будет управлять вашими подключениями 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. Посмотри на это.
Управляйте пулами соединений Монго в одном автономном модуле. Этот подход обеспечивает два преимущества. Во-первых, это делает ваш код модульным и его легче тестировать. Во-вторых, вам не нужно смешивать соединение с базой данных в объекте запроса, который НЕ является местом для объекта соединения с базой данных. (Учитывая природу JavaScript, я считаю очень опасным смешивать что-либо с объектом, созданным библиотечным кодом). Так что с этим вам нужно только рассмотреть модуль, который экспортирует два метода. connect = () => Promise
а также get = () => 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
Если вы настроите свой модуль БД таким же образом, как показано ниже, вы не только сможете гарантировать, что ваше приложение не загрузится, если у вас нет подключения к базе данных, у вас также есть глобальный способ доступа к пулу подключений к базе данных, который приведет к ошибке если у вас нет связи.
// 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;
}
Если у вас есть Express.js, вы можете использовать express-mongo-db для кэширования и совместного использования соединения MongoDB между запросами без пула (так как в принятом ответе говорится, что это правильный способ поделиться соединением).
Если нет - вы можете посмотреть на его исходный код и использовать его в другом фреймворке.
Вы должны создать соединение как сервис, а затем использовать его при необходимости.
// 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
Я использовал generic-pool с подключениями redis в своем приложении - я очень рекомендую его. Он универсален, и я точно знаю, что он работает с mysql, поэтому я не думаю, что у вас возникнут какие-либо проблемы с ним и монго
http://mongoosejs.com/docs/api.html
Проверьте источник мангуста. Они открывают соединение и связывают его с объектом Model, поэтому, когда требуется объект Model, устанавливается соединение с БД. Драйвер заботится о пуле соединений.
Если вы используете экспресс, есть еще один более простой метод, который заключается в использовании встроенной функции 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
и не требует создания дополнительных модулей.
Я реализовал приведенный ниже код в своем проекте, чтобы реализовать пул соединений в моем коде, чтобы он создал минимальное соединение в моем проекте и повторно использовал доступное соединение
/* 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");
});
Если кто-то хочет что-то, что работает в 2021 году с Typescript, вот что я использую:
import { MongoClient, Collection } from "mongodb";
const FILE_DB_HOST = process.env.FILE_DB_HOST as string;
const FILE_DB_DATABASE = process.env.FILE_DB_DATABASE as string;
const FILES_COLLECTION = process.env.FILES_COLLECTION as string;
if (!FILE_DB_HOST || !FILE_DB_DATABASE || !FILES_COLLECTION) {
throw "Missing FILE_DB_HOST, FILE_DB_DATABASE, or FILES_COLLECTION environment variables.";
}
const client = new MongoClient(FILE_DB_HOST, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
class Mongoose {
static FilesCollection: Collection;
static async init() {
const connection = await client.connect();
const FileDB = connection.db(FILE_DB_DATABASE);
Mongoose.FilesCollection = FileDB.collection(FILES_COLLECTION);
}
}
Mongoose.init();
export default Mongoose;
Я считаю, что если запрос поступит слишком рано (до
Mongo.init()
успевает закончить), будет выдана ошибка, т.к. будет undefined.
import { Request, Response, NextFunction } from "express";
import Mongoose from "../../mongoose";
export default async function GetFile(req: Request, res: Response, next: NextFunction) {
const files = Mongoose.FilesCollection;
const file = await files.findOne({ fileName: "hello" });
res.send(file);
}
Например, если вы позвоните
files.findOne({ ... })
а также
Mongoose.FilesCollection
не определено, то вы получите сообщение об ошибке.
Лучший подход для реализации пула соединений - вы должны создать одну переменную глобального массива, которая будет содержать имя базы данных с объектом соединения, возвращенным MongoClient, а затем повторно использовать это соединение всякий раз, когда вам нужно связаться с базой данных.
В вашем Server.js определите var global.dbconnections = [];
Создайте сервис именования connectionService.js. У него будет 2 метода getConnection и createConnection. Поэтому, когда пользователь вызовет getConnection(), он найдет детали в глобальной переменной соединения и вернет детали соединения, если уже существует, иначе он вызовет createConnection() и вернет детали соединения.
Вызовите этот сервис, используя 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;
}
npm i express mongoose
mongodb.js
const express = require('express');
const mongoose =require('mongoose')
const app = express();
mongoose.set('strictQuery', true);
mongoose.connect('mongodb://localhost:27017/db_name', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB Connected...'))
.catch((err) => console.log(err))
app.listen(3000,()=>{ console.log("Started on port 3000 !!!") })
node mongodb.js
https://mongodb.com/ -> новый проект -> новый кластер -> новая коллекция -> подключение -> IP-адрес: 0.0.0.0/0 & db cred -> подключите свое приложение -> скопируйте строку подключения и вставьте файл.env вашего узла app и не забудьте заменить "" фактическим паролем для пользователя, а также заменить "/test" своим именем базы данных
создать новый файл.env
CONNECTIONSTRING=x --> const client = new MongoClient(CONNECTIONSTRING)
PORT=8080
JWTSECRET=mysuper456secret123phrase
Используя приведенный ниже метод, вы можете легко управлять как можно большим количеством подключений.
var mongoose = require('mongoose');
//Set up default mongoose connection
const bankDB = ()=>{
return mongoose.createConnection('mongodb+srv://<username>:<passwprd>@mydemo.jk4nr.mongodb.net/<database>?retryWrites=true&w=majority',options);
}
bankDB().then(()=>console.log('Connected to mongoDB-Atlas bankApp...'))
.catch((err)=>console.error('Could not connected to mongoDB',err));
//Set up second mongoose connection
const myDB = ()=>{
return mongoose.createConnection('mongodb+srv://<username>:<password>@mydemo.jk4nr.mongodb.net/<database>?retryWrites=true&w=majority',options);
}
myDB().then(()=>console.log('Connected to mongoDB-Atlas connection 2...'))
.catch((err)=>console.error('Could not connected to mongoDB',err));
module.exports = { bankDB(), myDB() };