• MongoDB JavaScript 驅動器測試

    0
    JavaScript MongoDB Node.js C/C++ Go 18237 次瀏覽

    在對 node.js + MongoDB 做了一周不到的測試之后,我們決定放棄這對組合。放棄的原因有二:

    • MongoDB 對數據的保障性不是我們所需要的。這不是 MongoDB 的錯誤,這是我們選擇產品的錯誤。我覺得 MongoDB 其實就是放棄了這樣的數據保障性才獲得了更好的性能。所以才更適合類似 facebook twitter 對消息保障性要求不高,但是量大的應用。
    • Javascript 的 driver 略顯不成熟。其實各類開發速度都很快,同時我對他們的熟悉程度還不夠好。所以總的感覺現在還沒到用的時候。

    這里對第二點做個流水賬式樣的記錄,在學習的過程中發現相關的英文和中文資料都比較缺乏。

    我所測試到的 Driver 有:

    • node-mongodb-native
    • mongolian
    • mongoose

    這三個 Driver 里,mongolianmongoose 都是依賴 native 的。不過在這里mongolian的作者提到 mongolian對 native db class 部分并不調用。看來依賴的程度有所不一。

    所測試的內容是 failover。MongoDB 推薦的 failover 方案為 Replica Set,這個架構邏輯上不難理解。至少三個節點,至多七個節點;各個節點可以有 0-99 的優先級等一系列特性讓他成為非常優秀的 HA 方案。

    測試方法: 插入 N 條數據,并且在插入的過程中將 Primary 進程殺死。查看客戶端(node.js)是否正常轉移到新的 Primary ,并且最終檢查數據一致性。可以接受插入不了數據,但是一定要有錯誤返回。返回錯誤的數量一定要和數據庫內未插入的數據數量一致。

    一、native

    先給出 native 的測試腳本:

    var mongodb = require('mongodb');
    var Db = require('mongodb').Db,
      Connection = require('mongodb').Connection,
      Server = require('mongodb').Server,
      ReplSetServers = require('mongodb').ReplSetServers;
    
    var replStat = new ReplSetServers([
    	new Server('172.16.5.151', 28010, { auto_reconnect: true }),
    	new Server('172.16.5.152', 28010, { auto_reconnect: true }),
    	new Server('172.16.5.153', 28010, { auto_reconnect: true })
    	],
    	{rs_name: 'rs1'}
    );
    
    var db = new Db('a', replStat);
    db.open(function (error, client) {
      if (error) throw error;
      var collection = new mongodb.Collection(client, 'blogposts');
    
    function test_read(t)
    {
      console.log('enum elements...');
      var start = new Date;
      var times = 0;
      for(var i = 1; i <= t; i++ )
      {
    	collection.find({'_id':i}, {limit:1}).nextObject(function(err, docs) {
    	    if (err) console.warn(err.message);
    	    //else console.dir(docs);
    	    if(++times >= t)
    		console.log('enum finished:cost time:' + (new Date - start) + 'ms');
    	  });
      }
    }
    
    function test_write(t)
    {
      var start = new Date;
      var times = 0;
      console.log('add elements...');
      for(var i = 1; i <= t; i++ )
      {
      	collection.insert({date: (new Date()).getTime(), body:'sadf', title:'abc', _id:i}, {safe:{w:2, wtimeout: 10000}},
                        function(err, objects) {
    	    if (err) console.warn(err.message);
    	    if(++times >= t)
    	    {
    		console.log('add finished:cost time:' + (new Date - start) + 'ms');
    		test_read(t);
    	    }
      	});
      }
    }
    
    var wtimes = 10000;
    test_write(wtimes);
    //test_read(wtimes);
    });
    三個 driver 中文檔工作做的最好的就是 native 了,example 也比較多。不過作者在 Replica Set 的 examples 中給了個讓人很莫名的開頭:
    var port1 = 27018;
    var port2 = 27019;
    var server = new Server(host, port, {});
    var server1 = new Server(host, port1, {});
    var server2 = new Server(host, port2, {});
    var servers = new Array();
    servers[0] = server2;
    servers[1] = server1;
    servers[2] = server;
    
    var replStat = new ReplSetServers(servers);

    對于我這種不寫代碼的人來說,您老寫成這樣著實讓我糾結了一番??

    測試結果: 在插入的過程中將 Primary kill 后大約有 1/3 的概率 node.js crash 了。其余 2/3 的概率 node.js 徹底卡住。MongoDB 端 Primary 正常轉移,但未見數據繼續插入進來。我很想貼一點 log 上來,但 native driver 真的沒有任何 log,就是單純的卡住了??卡住??卡??

    Crash log:

    [root@localhost bin]# ./node ~/native_test.js
    2
    3
    4
    add elements...
    node: src/uv-common.c:92: uv_err_name: Assertion `0' failed.
    已放棄

    在經過幾天的搜索以后[1 2 3 ] 我發現似乎有人和我做過類似的測試,但是從來沒有得到明確的答案。昨天我也將這個問題發到的 native 論壇上,目前還沒有人回復。
    但是后來又隨后開始懷疑自己的腳本,同時看到新的解答[4], 于是開始嘗試不在一個 db.open 里面寫 for,而在 for 里面反復的 db.open 和 db.close。但是沒有成功,循環插入10條數據,成功插入的只有第一條。無論有沒有 db.close 都是這個現象。這個不工作的代碼就不貼上來了,如果有那位做過類似測試希望可以交流一下。

    二、mongoose

    測試腳本:

    var mongoose = require('mongoose');
    mongoose.createSetConnection('mongodb://172.16.5.151:28010/a,mongodb://172.16.5.152:28010/a,mongodb://172.16.5.153:28010/a');
    
    var Schema = mongoose.Schema,
        ObjectId = Schema.ObjectId;
    
    var BlogPost = new Schema({
    //    author    : ObjectId
        _id       : Number
      , title     : String
      , body      : String
      , date      : Date
    });
    
    mongoose.model('BlogPost', BlogPost);
    var post = mongoose.model('BlogPost');
    
    function test_read(t)
    {
    	var start = new Date();
    	var times = 0;
    	console.log('enum elements...');
    	for(var i = 1; i <= t; i++)
    	{
    		//console.log("read:"+i);
    		post.findById(i, function(err, doc){
    			if(err)
    				console.log(err);
    			//else
    			//	console.log(doc);
    			if(++times >= t)
    			{
    				var end = new Date();
    				console.log('enum finished:cost time:' + (end - start) + "ms");
    			}
    		});
    	}
    };
    
    function test_write(t)
    {
    	var start = new Date();
    	var times = 0;
    	console.log('add elements...');
    	for(var i = 1; i <= t; i++)
    	{
    		//console.log("write:"+i);
    
    		var p = new post();
    		p._id = i;
    		p.title = 'abc';
    		p.body = 'sadf';
    		p.date = (new Date()).getTime();
    		p.save(function(err){
    			if(err)
    			{
    				console.log(err);
    			}
    			if(++times >= t)
    			{
    				var end = new Date();
    				console.log('add finished:cost time:' + (end - start) + "ms");
    				test_read(t);
    			}
    		});
    	}
    }
    
    var wtimes = 10000;
    test_write(wtimes);
    //process.exit(0);
    首先!連接 Replica Set 要用createSetConnection:
    mongoose.createSetConnection('mongodb://172.16.5.151:28010/a,mongodb://172.16.5.152:28010/a,mongodb://172.16.5.153:28010/a');

    你或許和我一樣走過一些彎路[5 6]。

    測試結果: OSE 的測試結果幾乎和 native 一樣,唯一好一點的是它從來沒把 node.js 弄 crash 過。它唯一的反應就是 卡住??卡住??
    OSE 和 native 在這個測試上的區別是,native 一邊產生數據一邊插入。OSE 先將數據在內存中產生出來以后,再一次插入數據庫。而 node.js 存在一個內存限制的問題 (一個瀏覽器有什么理由需要2G的內存呢?),所以當 OSE driver 占用超過 1.9G 內存之后,node.js 不出意料的 crash。

    PS. google 論壇上有人說可以通過參數讓 node.js 支持任何大小的內存。經過我的測試(CentOS 6 x86-64,0.5.x,0.4.x)沒有成功過。可工作的最高數值為 1900M。

    你可以注意到了 native 驅動有一個 auto_reconnect 參數(盡管它沒有 reconnect),而 mongoose 腳本里面沒看到。OSE 的確也有設置 auto_reconnect 的方式[7], 但是只看到給普通連接設置的方式。沒有看到給 Replica Set 用的方式。自己胡亂嘗試了幾個設置方式無一成功。希望 ose 的作者能再多花點時間在文檔方面。另一方面也可以看到,OSE 其實對 native 依賴還是蠻嚴重的。這種設置方式的出現似乎只是傳遞給 native 驅動,我猜測 OSE 自己沒有對這塊做任何處理。

    三、mongolian

    測試腳本:

    var mongodb = require('mongolian');
    var server = new mongodb(
        "172.16.5.151:28010",
        "172.16.5.152:28010",
        "172.16.5.153:28010"
    )
    var db = server.db("a")
    var blogposts = db.collection("blogposts")
    
    function test_read(t)
    {
      console.log('enum elements...');
      var start = new Date;
      var times = 0;
      for(var i = 1; i <= t; i++ )
      {
    	blogposts.find({'_id':i}, {limit:1}).nextObject(function(err, docs) {
    	    if (err) console.warn(err.message);
    	    //else console.dir(docs);
    	    if(++times >= t)
    		console.log('enum finished:cost time:' + (new Date - start) + 'ms');
    	  });
      }
    }
    
    function test_write(t)
    {
      var start = new Date;
      var times = 0;
      console.log('add elements...');
      for(var i = 1; i <= t; i++ )
      {
      	blogposts.insert({date: (new Date()).getTime(), body:'sadf', title:'abc', _id:i},
                        function(err, objects) {
    	    if (err) console.warn(err.message);
    	    if(++times >= t)
    	    {
    		console.log('add finished:cost time:' + (new Date - start) + 'ms');
    		test_read(t);
    	    }
      	});
      }
    }
    
    var wtimes = 10000;
    test_write(wtimes);
    //test_read(wtimes);
    測試結果: mongolian(以下簡稱lian) 的反應是這三個驅動中最好的。首先當開啟 node 的時候,lian 會給出 debug 信息,明確告訴你他連接到了哪臺 mongodb,作者也明確說了這個 log 是為 Replica Set 做的 [89]。當 Primary 被 kill 掉之后,lian 會告訴你連接丟失。在后面的插入lian會明確的告訴你插入失敗,并且是每一次插入就給出一個 log,而且程序會一路走下去,不會卡住。
    [root@localhost ~]# node lian_test.js
    add elements...
    [debug] mongo://172.16.5.151:28010: Disconnected
    [error] mongo://172.16.5.151:28010: Error: ECONNREFUSED, Connection refused
    [debug] mongo://172.16.5.152:28010: Connected
    [debug] mongo://172.16.5.153:28010: Connected
    [debug] mongo://172.16.5.152:28010: Initialized as secondary
    [debug] mongo://172.16.5.153:28010: Initialized as primary
    [info] mongo://172.16.5.153:28010: Connected to primary
    [debug] Finished scanning... primary? mongo://172.16.5.153:28010

    我覺得 lian 的這種工作模式可以從它的代碼編寫方式里面體現出來。lian 的代碼里面不存在打開一個 connection 或者 db.open 這樣的概念,所以我估計 lian 是每一次 insert 就會嘗試打開一次 connection。雖然他沒有再次找到正確的 Primary,但至少他知道自己連接丟失了。
    但是 lian 沒能再次找到正確的 Primary 可能意味著他先打開了一個 ConnectionPool (你可以通過 poolSize 在 native 里面設置 pool 的大小),只有打開 ConnectionPool 的時候才會嘗試去做 Primary 判斷。

    另外 lian 的插入速度也不錯,感覺比 OSE 好,幾乎和 native 一樣。

    實驗做到后面,我極度懷疑自己的測試腳本寫的不對。因為 native 是有 auto_reconnect 的參數的,但是缺沒有工作。作者應該考慮了這個問題的。

    而也肯定有一種方式讓我在 for 里面打開 connection 、寫完、關閉 connection。只是我現在沒找到正確的寫法。

    希望有經驗的朋友給予一些幫助。

    原文出處:http://latteye.com/2011/10/mongodb-javascript-driver.html

    相似問題

    相關經驗

    相關資訊

    相關文檔

  • sesese色