用generic-pool连接的Mongodb,运行大概一天后报错,求Nodejs解决方案

用generic-pool连接的Mongodb,运行大概一天后报错,求Nodejs解决方案

用generic-pool连接的Mongodb, 运行大概一天后报这个错:

db object already connecting, open cannot be called multiple times

用了:cluster, generic-pool, mongodb, 它是运行一天后才出会现,而且访问的时候有些慢(其中有一个方法是查了4次mongodb);

我把这个方法(就是一个纯接口) 改成查2次,就没有这个问题了,而且也快了,为什么差别这么大?

pool是这么用的:

exports.dbpool = pool = poolModule.Pool({ name : ‘mongo’, create : function(callback) { dbutil.createMongoClient(‘test’,function(err, db, client){ callback(err,client); }); }, destroy : function(client) { client.close(); }, max : 2, idleTimeoutMillis :100, log : false });

不知道这个配置是否有问题?

我在网上查了2天了,也没有查出正解;

请大拿们给正解; 谢谢;


12 回复

根据你描述的问题,错误信息 db object already connecting, open cannot be called multiple times 表明在尝试多次打开同一个数据库连接时出现了问题。这通常是因为你尝试在同一个客户端上多次调用 open 方法,而 MongoDB 的客户端设计并不支持这种操作。

为了解决这个问题,我们需要确保每次从连接池中获取到的客户端都是已经正确初始化并处于可用状态的。此外,为了提高性能和避免不必要的重复查询,我们可以优化查询逻辑。

以下是一些可能的解决方案:

  1. 确保每个请求使用独立的客户端实例:即使你从连接池中获取了一个客户端,也需要确保每个请求都使用独立的客户端实例进行操作,而不是复用同一个客户端实例。

  2. 优化查询逻辑:减少对数据库的重复查询可以显著提高性能。如果可以的话,尽量合并查询或缓存查询结果。

  3. 调整连接池配置:根据你的应用负载调整连接池的最大连接数 (max) 和空闲超时时间 (idleTimeoutMillis)。

示例代码

首先,我们来改进连接池的配置,并确保每次查询时使用独立的客户端实例。

const genericPool = require('generic-pool');
const MongoClient = require('mongodb').MongoClient;

// 创建连接池
const poolModule = genericPool.Pool;
exports.dbpool = pool = poolModule.Pool({
    name: 'mongo',
    create: async () => {
        const client = await MongoClient.connect('mongodb://localhost:27017/test', { useNewUrlParser: true, useUnifiedTopology: true });
        return client.db('test'); // 返回数据库实例
    },
    destroy: async (client) => {
        await client.close(); // 关闭数据库连接
    },
    max: 10, // 最大连接数
    min: 2, // 最小连接数
    idleTimeoutMillis: 30000, // 空闲超时时间
    log: false
});

// 使用连接池的方法
async function queryData() {
    const client = await exports.dbpool.acquire(); // 从连接池获取客户端
    try {
        const result = await client.collection('yourCollection').find({}).toArray();
        console.log(result);
    } finally {
        exports.dbpool.release(client); // 将客户端归还给连接池
    }
}

// 执行查询
queryData().catch(console.error);

在这个示例中,我们创建了一个连接池,并且在每次查询时都从连接池中获取一个独立的客户端实例。这样可以确保不会出现重复连接的问题,并且保持了连接池的灵活性。

通过这种方式,你可以有效地解决连接问题,并优化查询逻辑以提高应用的整体性能。


报这个错误后,node.js 就不提供服务了,但进程并没有挂掉;

貌似是没有关闭数据库连接,检查下代码,看看哪里的bug导致了这个问题。

数据库不关闭,不是更能提高单次打开的利用率么? 这个必须要关闭么?

有没有可能跟这个generic-pool有关系?

其中有一个方法是查了4次mongodb , 改成查2次,就没有这个问题了 和配置里面 max : 2 不觉得存在一定的因果关系吗。个人猜测这4次查询是并发,即几乎同一时间申请4个数据库连接,但是配置里面max = 2,于是乎。。。 PS: mongodb驱动自带连接池,外面有必要再套个generic-pool的连接池吗?

我用:max = 10 也会有同样的情况,只是刚开始较快一些,慢慢的就慢了,它还有一种比较奇怪的现象是:连续请求这个接口几次(基本上7次以内)后,会有一次不能响应的;然后再刷就又可以了(就是慢);

generic-pool的连接池是项目中在用,我暂时还不敢(我刚入门)把它去掉,因为这个模式已经跑一段时间了;

把能打开的日志全打开,跟踪连接池的连接数目出入,监视node的内存占用率(process.memoryUsage()),内存占用率一直增长却从不下降的话,很大可能是内存泄露(memory leak),有数据库连接没有被连接池回收。 可以的话,还是把api接口的代码贴出来吧,去掉业务逻辑,只保留数据库的相关操作

generic-pool我用了很长时间,没有你说的这个问题的,不知道你用的是哪个mongodb链接库,这个错误应该是你的mongodb库报的 原因可能是你实例化了一个mongodb连接,然后反复使用这个实例去连接数据库,伪代码可能如下:

var db = new mongodb('127.0.0.1',27017)  //创建一个mongodb实例
db.connection()
db.connection() //反复调用connection,可能就会出现上面你说的错误.

所以使用generic-pool的注意:

  1. 连接使用完毕,一定要将连接放回连接池,不论是否有error错误
  2. 你在实例化连接池,注册create时,要保证里面的函数每次都是一个新实例,不要用单例,反复调用connect方法,这样就等于一个连接,还可能出错
  3. 注册create时,一定要根据api那样写,callback(err,db)err,这种

最后给你个地址,我用mongodb的native库配合generic-pool做的数据库连接文件,你可以参考哦 https://github.com/DoubleSpout/rrestjs/blob/master/lib/MongdbConnect.js

谢谢,那我跟踪一下连接池数目;

谢谢,我参考一下你的这个,然后再试试效果!~

你的这个用 generic-pool 做 mongodb 的连接池,有遇到性能问题么?

我按照你的这个方便弄了一个, 压 1000并发, 60秒, 中间有的请求就没有响应了; 刷了几次后,发现又可以进行正常请求了!~

求真正原因?

从描述来看,错误信息 db object already connecting, open cannot be called multiple times 提示你尝试多次打开同一个数据库连接。这可能是由于你在 createMongoClient 方法中尝试重复地打开数据库连接。

以下是解决方法及建议的改进:

解决方法

  1. 确保每次创建客户端时都只初始化一次

    • createMongoClient 中确保你没有在已经打开的连接上再次调用 openopenUrl
  2. 使用已有的连接

    • 如果连接已经在 generic-pool 中,那么应该复用该连接,而不是每次都重新创建。

示例代码

const MongoClient = require('mongodb').MongoClient;
const poolModule = require('generic-pool');

// 创建MongoDB客户端
function createMongoClient(databaseName, callback) {
    MongoClient.connect(`mongodb://localhost:27017/${databaseName}`, (err, client) => {
        if (err) return callback(err);
        const db = client.db(databaseName);
        callback(null, db, client);
    });
}

// 配置连接池
exports.dbpool = poolModule.Pool({
    name: 'mongo',
    create: function (callback) {
        createMongoClient('test', function (err, db, client) {
            if (err) return callback(err);
            callback(null, { db, client });
        });
    },
    destroy: function (client) {
        client.client.close();
    },
    max: 2,
    idleTimeoutMillis: 30000,
    log: false
});

// 使用连接池查询数据
async function queryData() {
    try {
        const client = await exports.dbpool.acquire();
        const result = await client.db.collection('collectionName').find({}).toArray();
        exports.dbpool.release(client);
        return result;
    } catch (error) {
        console.error(error);
        throw error;
    }
}

解释

  • createMongoClient: 这个函数确保每个数据库连接只被创建一次,并返回数据库实例。
  • poolModule.Pool: 这里配置了连接池,设置最大连接数为2,并设置了连接超时时间。
  • queryData: 这个异步函数演示了如何从连接池中获取连接、执行查询并释放连接。

注意事项

  • 确保在每次操作完成后释放连接,以避免资源泄露。
  • 检查是否有其他地方也在创建数据库连接,这可能导致重复连接问题。
  • 如果查询性能是个问题,可以考虑优化查询或增加连接池的最大连接数(但要注意不要过度增加,以免对数据库造成压力)。

这样可以避免重复打开数据库连接,从而解决错误。

回到顶部