如何在Node.js web应用程序中管理MongoDB连接?

我正在使用MongoDB的node-mongodb-native驱动程序来编写一个网站。

我有一些关于如何管理连接的问题:

  1. 对所有请求只使用一个MongoDB连接够吗?是否存在性能问题?如果不是,我能在整个应用程序中设置一个全局连接吗?

  2. 如果不是,如果我在请求到达时打开一个新的连接,并在处理请求时关闭它,这是好的吗?打开和关闭连接的成本高吗?

  3. 我应该使用全局连接池吗?我听说驱动程序有一个本地连接池。这是一个好的选择吗?

  4. 如果我使用连接池,应该使用多少连接?

  5. 还有什么需要我注意的吗?

147940 次浏览

我一直在我的应用程序中使用redis连接的通用池-我强烈推荐它。它是通用的,我肯定知道它适用于mysql,所以我不认为你会有任何问题与它和mongo

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

node-mongodb-native的主提交者说:

打开蒙古客户端。连接一次,当你的应用程序启动和重用 db对象。每个.connect并不是一个单独的连接池 创建一个新的连接池

所以,直接回答你的问题,重用MongoClient.connect()的db对象。这为您提供了池化,与在每个db操作上打开/关闭连接相比,将提供显著的速度提高。

如果你有Express.js,你可以使用express-mongo-db在没有池的请求之间缓存和共享MongoDB连接(因为接受的答案说这是共享连接的正确方式)。

如果没有,你可以查看它的源代码并在另一个框架中使用它。

在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文档。看一看。

在单个自包含模块中管理mongo连接池。这种方法有两个好处。首先,它使你的代码模块化,更容易测试。其次,你不会被迫在请求对象中混合你的数据库连接,这不是数据库连接对象的地方。(考虑到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);
});

当你的应用在运行时,当它需要一个DB连接时,你可以简单地调用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;
}

我已经在我的项目中实现了下面的代码,在我的代码中实现连接池,这样它将在我的项目中创建一个最小的连接,并重用可用的连接

/* 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");


});

您应该将连接创建为服务,然后在需要时重用它。

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

实现连接池的最佳方法是创建一个全局数组变量,其中包含db名称和MongoClient返回的连接对象,然后在需要联系数据库时重用该连接。

  1. 在你的Server.js中定义var global.dbconnections = [];

  2. 创建命名为connectionService.js的服务。它将有两个方法getConnectioncreateConnection。 因此,当用户调用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;
}

如果使用express,还有另一种更直接的方法,即利用express的内置功能在应用程序中的路由和模块之间共享数据。有一个名为app.locals的对象。我们可以给它附加属性,并从我们的路由内部访问它。要使用它,在app.js文件中实例化你的mongo连接。

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访问,不需要创建任何额外的模块。

如果有人想在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()有时间完成之前),将抛出一个错误,因为Mongoose.FilesCollection将是未定义的。

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未定义,则会得到一个错误。

使用下面的方法,您可以轻松管理尽可能多的连接

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