21

我使用 10gen 的本机 node.js 驱动器将 node.js 与 mongodb (2.2.2) 一起尝试。

起初一切都很顺利。但是当来到并发基准测试部分时,出现了很多错误。频繁连接/关闭 1000 个并发可能会导致 mongodb 拒绝任何进一步的请求,错误如下:

Error: failed to connect to [localhost:27017]

Error: Could not locate any valid servers in initial seed list

Error: no primary server found in set

此外,如果许多客户端在没有明确关闭的情况下关闭,则需要 mongodb 分钟来检测并关闭它们。这也会导致类似的连接问题。(使用 /var/log/mongodb/mongodb.log 查看连接状态)

我已经尝试了很多。根据手册,mongodb 没有连接限制,但poolSize选项似乎对我没有影响。

由于我只在 node-mongodb-native 模块中使用它,我不太确定最终导致问题的原因。其他语言和驱动程序的性能如何?

PS:目前,使用自维护池是我想出的唯一解决方案,但使用它并不能解决副本集的问题。根据我的测试,副本集似乎比独立 mongodb 需要更少的连接。但不知道为什么会这样。

并发测试代码:

var MongoClient = require('mongodb').MongoClient;

var uri = "mongodb://192.168.0.123:27017,192.168.0.124:27017/test";

for (var i = 0; i < 1000; i++) {
    MongoClient.connect(uri, {
        server: {
            socketOptions: {
                connectTimeoutMS: 3000
            }
        },
    }, function (err, db) {
        if (err) {
            console.log('error: ', err);
        } else {
            var col = db.collection('test');
            col.insert({abc:1}, function (err, result) {
                if (err) {
                    console.log('insert error: ', err);
                } else {
                    console.log('success: ', result);
                }
                db.close()
            })
        }
    })
}

通用池解决方案:

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

var uri = "mongodb://localhost/test";

var read_pool = poolModule.Pool({
    name     : 'redis_offer_payment_reader',
    create   : function(callback) {
        MongoClient.connect(uri, {}, function (err, db) {
            if (err) {
                callback(err);
            } else {
                callback(null, db);
            }
        });
    },
    destroy  : function(client) { client.close(); },
    max      : 400,
    // optional. if you set this, make sure to drain() (see step 3)
    min      : 200, 
    // specifies how long a resource can stay idle in pool before being removed
    idleTimeoutMillis : 30000,
    // if true, logs via console.log - can also be a function
    log : false 
});


var size = [];
for (var i = 0; i < 100000; i++) {
    size.push(i);
}

size.forEach(function () {
    read_pool.acquire(function (err, db) {
        if (err) {
            console.log('error: ', err);
        } else {
            var col = db.collection('test');
            col.insert({abc:1}, function (err, result) {
                if (err) {
                    console.log('insert error: ', err);
                } else {
                    //console.log('success: ', result);
                }
                read_pool.release(db);
            })
        }
    })
})
4

2 回答 2

23

由于 Node.js 是单线程的,因此您不应该在每个请求上打开和关闭连接(就像您在其他多线程环境中所做的那样。)

这是编写 MongoDB node.js 客户端模块的人的引述:

“当你的应用程序启动并重用 db 对象时,你打开 do MongoClient.connect 一次。它不是一个单例连接池,每个 .connect 都会创建一个新的连接池。所以打开它一次,[d] 在所有请求中重用。” - christkv https://groups.google.com/forum/#!msg/node-mongodb-native/mSGnnuG8C1o/Hiaqvdu1bWoJ

于 2013-03-28T17:41:40.870 回答
4

在听取了赫克托的建议后。我发现 Mongodb 的连接与我曾经使用过的其他一些数据库完全不同。主要区别是nodejs中的本机驱动器:MongoClient对于每个打开的MongoClient都有自己的连接池,池大小由定义

server:{poolSize: n}

因此,使用 poolSize:100 打开 5 个 MongoClient 连接,意味着总共 5*100=500 个到目标 Mongodb Uri 的连接。在这种情况下,频繁的打开和关闭 MongoClient 连接肯定会给主机带来巨大的负担,最终导致连接问题。这就是为什么我一开始就遇到这么多麻烦。

但是由于我的代码是这样编写的,所以我使用连接池来存储具有每个不同 URI 的单个连接,并使用与 poolSize 大小相同的简单并行限制器,以避免负载峰值获取连接错误。

这是我的代码:

/*npm modules start*/
var MongoClient = require('mongodb').MongoClient;
/*npm modules end*/

// simple resouce limitation module, control parallel size
var simple_limit = require('simple_limit').simple_limit; 

// one uri, one connection
var client_pool = {};

var default_options = {
    server: {
        auto_reconnect:true, poolSize: 200,
        socketOptions: {
            connectTimeoutMS: 1000
        }
    }
}

var mongodb_pool = function (uri, options) {
    this.uri = uri;
    options = options || default_options;
    this.options = options;
    this.poolSize = 10; // default poolSize 10, this will be used in generic pool as max

    if (undefined !== options.server && undefined !== options.server.poolSize) {
        this.poolSize = options.server.poolSize;// if (in)options defined poolSize, use it
    }
}

// cb(err, db)
mongodb_pool.prototype.open = function (cb) {
    var self = this;
    if (undefined === client_pool[this.uri]) {
        console.log('new');

        // init pool node with lock and wait list with current callback
        client_pool[this.uri] = {
            lock: true,
            wait: [cb]
        }

        // open mongodb first
        MongoClient.connect(this.uri, this.options, function (err, db) {
            if (err) {
                cb(err);
            } else {
                client_pool[self.uri].limiter = new simple_limit(self.poolSize);
                client_pool[self.uri].db = db;

                client_pool[self.uri].wait.forEach(function (callback) {
                    client_pool[self.uri].limiter.acquire(function () {
                        callback(null, client_pool[self.uri].db)
                    });
                })

                client_pool[self.uri].lock = false;
            }
        })
    } else if (true === client_pool[this.uri].lock) {
        // while one is connecting to the target uri, just wait
        client_pool[this.uri].wait.push(cb);
    } else {
        client_pool[this.uri].limiter.acquire(function () {
            cb(null, client_pool[self.uri].db)
        });
    }
}

// use close to release one connection
mongodb_pool.prototype.close = function () {
    client_pool[this.uri].limiter.release();
}

exports.mongodb_pool = mongodb_pool;
于 2013-03-30T03:28:00.473 回答