Как правильно обращаться с Mongodb-соединениями?

Я пытаюсь использовать node.js вместе с mongodb (2.2.2), используя собственный диск node.js от 10gen.

Сначала все прошло хорошо. Но когда мы подошли к тестированию параллелизма, возникло много ошибок. Частое соединение / закрытие с 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);
            })
        }
    })
})

2 ответа

Решение

Поскольку Node.js является однопоточным, вы не должны открывать и закрывать соединение при каждом запросе (как вы это делаете в других многопоточных средах).

Это цитата человека, написавшего клиентский модуль MongoDB node.js:

"Вы открываете do MongoClient.connect один раз, когда ваше приложение загружается, и повторно используете объект db. Это не пул одноэлементных соединений, каждый.connect создает новый пул соединений. Поэтому откройте его один раз и [d] повторно используйте для всех запросов ". - christkv https://groups.google.com/forum/#!msg/node-mongodb-native/mSGnnuG8C1o/Hiaqvdu1bWoJ

Посмотрев на совет Гектора. Я считаю, что подключение Mongodb сильно отличается от некоторых других баз данных, которые я когда-либо использовал. Основным отличием является собственный диск в nodejs: у MongoClient есть собственный пул соединений для каждого открытого MongoClient, размер пула которого определяется

server:{poolSize: n}

Итак, откройте 5 подключений MongoClient с poolSize:100, значит всего 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;
Другие вопросы по тегам