2013-03-28 110 views
21

我使用10gen的native node.js驅動器一起嘗試使用mongodb(2.2.2)的node.js。處理Mongodb連接的正確方法是什麼?

起初一切進展順利。但是當進入併發基準測試部分時,出現了很多錯誤。頻繁連接/接近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); 
      }) 
     } 
    }) 
}) 

回答

22

由於Node.js的是單線程你不應該被打開和關閉對每個請求的連接(如你會在其他多線程環境中執行)。

這是寫了MongoDB node.js客戶端模塊的人的一句話:

「當您的應用啓動並重用 db對象時,您將打開一次MongoClient.connect。它不是每個連接池.connect 創建一個新的連接池。因此,打開它一旦[d]在所有 請求重用「 - christkv https://groups.google.com/forum/#!msg/node-mongodb-native/mSGnnuG8C1o/Hiaqvdu1bWoJ

+0

雖然Node.js的使用一個單一的過程和事件模型,但IO動作應該永遠是異步。我認爲這可能不是我的問題的原因。但是,我會嘗試你引用的方式,謝謝。 – Jack

+2

在第一個例子中,你錯過了這一點;該代碼相當於啓動您的Mongo應用1000次。您應該只在應用中調用.connect一次。 –

+0

是的,這是問題,原因是MongoClient擁有一個連接池本身,它比其他dbs更昂貴。但不是node.js單一過程模型的問題。 – Jack

3

尋找到Hector的提醒後。我發現Mongodb的連接與我曾經使用的一些其他數據庫完全不同。主要的區別是在的NodeJS本地驅動器:MongoClient都有它自己的連接池每個MongoClient開業,這池大小由

server:{poolSize: n} 

所以定義,poolSize開5 MongoClient連接:100,意味着總共5 * 100 =與目標Mongodb Uri的500個連接。在這種情況下,頻繁開放的&關閉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; 
+0

你好,我遇到過這個問題。你可以通過'var simple_limit = require('simple_limit')。simple_limit; 'simple_limit.js代碼?以及如何使用'mongodb_pool'?謝謝〜 – Eddy

相關問題