我使用10gen的本地node.js驱动器一起尝试使用mongodb(2.2.2)的node.js。
起初一切顺利。但是当进入并发基准测试部分时,发生了很多错误。经常连接/关闭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);
})
}
})
})
答案 0 :(得分:21)
由于Node.js是单线程的,因此您不应该在每个请求上打开和关闭连接(就像在其他多线程环境中那样)。
这是来自编写MongoDB node.js客户端模块的人的引用:
“当您的应用启动并重复使用时,您可以打开一次MongoClient.connect db对象。它不是每个单独的连接池.connect 创建一个新的连接池。因此,一旦[d]重复使用,就打开它 请求。“ - christkv https://groups.google.com/forum/#!msg/node-mongodb-native/mSGnnuG8C1o/Hiaqvdu1bWoJ
答案 1 :(得分:3)
在研究赫克托尔的建议之后。我发现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;