2013-03-28 22 views
21

Ich versuche node.js mit mongodb (2.2.2) zusammen mit dem nativen node.js Laufwerk von 10gen.Was ist der richtige Weg, um mit Mongodb-Verbindungen umzugehen?

Am Anfang lief alles gut. Aber als wir zum Concurrency-Benchmarking-Teil kamen, traten viele Fehler auf. Häufiges connect/Schließen mit 1000 Nebenläufigkeiten verursachen kann mongodb ablehnen weitere Wünsche mit Fehlern wie:

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 

Auch wenn viele Kunden Abschaltung ohne explizite schließt, wird es mongodb Minuten, um sie zu erkennen und zu schließen. Das wird auch ähnliche Verbindungsprobleme verursachen. (Mit /var/log/mongodb/mongodb.log, um den Verbindungsstatus zu überprüfen)

Ich habe viel versucht. Laut dem Handbuch, mongodb keine Verbindungsbeschränkung, aber poolSize Option scheint keine Auswirkungen auf mich zu haben.

Da ich nur mit dem node-mongodb-nativen Modul damit gearbeitet habe, bin ich mir nicht sicher, was letztendlich das Problem verursacht hat. Was ist mit der Leistung in anderen Sprachen und Treibern?

PS: Momentan ist die Verwendung eines selbstverwalteten Pools die einzige Lösung, die ich herausgefunden habe, aber die Verwendung kann das Problem mit dem Replikat nicht lösen. Laut meinem Test scheint Replica Set viel weniger Verbindungen als Standalone Mongodb zu nehmen. Aber habe keine Ahnung, warum das passiert.

Concurrency Testcode:

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() 
      }) 
     } 
    }) 
} 

generic-Pool-Lösung:

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); 
      }) 
     } 
    }) 
}) 

Antwort

22

Da Node.js ist Single-Threaded sollten Sie nicht auf jede Anforderung die Verbindung werden das Öffnen und Schließen (wie Sie würde in anderen multi-threaded Umgebungen tun.)

Dies ist ein Zitat von der Person, die das MongoDB node.js Client-Modul schrieb:

"Sie öffnen MongoClient.connect einmal, wenn Ihre App hochfährt und das DB-Objekt wiederverwenden. Es ist kein Singleton Verbindungspool jeder .connect erstellt einen neuen Verbindungspool. So öffnen Sie es einmal ein [d] Wiederverwendung in allen Anfragen.“- christkv https://groups.google.com/forum/#!msg/node-mongodb-native/mSGnnuG8C1o/Hiaqvdu1bWoJ

+0

Obwohl Node.js einen einzigen Prozess und Ereignismodell verwendet, aber IO Maßnahmen sollten immer async sein. Was meiner Meinung nach nicht die Ursache meiner Probleme sein könnte. Aber ich werde versuchen, wie du zitiert hast, danke. – Jack

+2

Sie vermissen den Punkt, in Ihrem ersten Beispiel; Dieser Code entspricht dem 1000-fachen Starten Ihrer Mongo-App. Sie sollten .connect nur einmal in Ihrer App anrufen. –

+0

Ja, das ist ein Problem, und der Grund ist, dass MongoClient einen Verbindungspool selbst hält, der teurer wird als andere dbs. Aber nicht das Problem mit dem Einzelprozessmodell von node.js. – Jack

3

Nach einem Blick in Hectors beraten. Ich finde Mongodb's Verbindung ist ziemlich verschieden von einigen anderen Datenbanken, die ich jemals benutzt habe. Der Hauptunterschied stammt Laufwerk in NodeJS: MongoClient seine eigenen Verbindungspool für jede MongoClient geöffnet, die Pool-Größe So von

server:{poolSize: n} 

definiert ist, offen 5 MongoClient Verbindung mit Poolsize: 100, bedeutet insgesamt 5 * 100 = 500 Verbindungen zum Ziel Mongodb Uri. In diesem Fall werden häufig offene & MongoClient-Verbindungen eine große Last für den Host sein und schließlich Verbindungsprobleme verursachen. Deshalb habe ich in erster Linie so viel Ärger bekommen.

Aber da mein Code in diese Weise geschrieben wurde, verwende ich einen Verbindungspool, um eine einzelne Verbindung mit jedem einzelnen URI zu speichern, und verwende einen einfachen parallelen Begrenzer gleicher Größe wie poolSize, um Verbindungsspitzenfehler zu vermeiden .

Hier ist mein Code:

/*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

Hallo, ich bin auf dieses Problem gestoßen. und könntest du die 'var simple_limit = require (' simple_limit ') übertreffen. 'simple_limit.js Code? und wie benutzt man 'mongodb_pool'? Danke ~ – Eddy

Verwandte Themen