MongoDB的自訂序列化(Customizing serialization)

來源:互聯網
上載者:User

我最近一直在研究MongoDB,有些小心得。恰好發現原來部落格園支援Live writer啊

興奮異常,終於多年以後重回這裡。以前一直用liver writer寫 myspace和 wordpress

但是前者完了,後者翻牆很煩。

====================================================

首先推薦一個MongoDB的查詢分析器

MongoVUE

這個工具是非常好用,雖然超過試用期,但是仍然可以使用

只是只能開三個查詢時段而已。

 

 

以前一直使用db4o, protobuf.net  ,所以對mongoDB還是很適應的。

因為相似性太大。尤其是對象持久化的方式,細節略微不同而已。

 

=============================================

1.需求:

我的一個新寫的演算法需要讀取一個完整的collection,而這需要幾十秒鐘。

而一開始都是使用特性標註的自動序列化和還原序列化,無論用任何方式調整,InsertBatch和

FindAll() 的效能都得不到提高。

 

2.思考:

我一開始以為讀取速度和儲存奇慢無比,是因為mongoDB自己的問題。今天仔細想了想。問題關鍵在於寫入硬碟的資料太多。

mongoDB的資料持久化是以BSON格式的。而這種格式的冗餘還是相當大的。尤其是預設序列化和還原序列化。

 

"_id" : ObjectId("4f4e2a02c992571e54c30465"),
"value" : "xxxxx",
"chars" : [{
"words" : [{
"index" : 0,
"length" : 2,
"wordTypes" : 0
}]
}, {
"words" : [{
"index" : 0,
"length" : 2,
"wordTypes" : 0
}, {
"index" : 1,
"length" : 2,
"wordTypes" : 0
}]
},

 

用mongoVUE查看最終資料格式,發覺主要儲存空間消耗在意義不大的屬性name上。計算一下就可以知道,名稱幾乎是值的5-10倍空間大小。

相比 protobuf,採用數字作為屬性的名稱,就十分節省空間的了。

但是mongodb可以檢索欄位,而protobuf不可以,所以mongo沒有採用protobuf的方式。

 

我有一個collection有50000個document,平均一個document  4000byte,這真是令人吃驚的低效持久化啊。怪不得讀取都需要幾十秒鐘。整個資料存放區消耗了200m空間。

 

由於看過mongoDB的官方文檔

http://www.mongodb.org/display/DOCS/CSharp+Language+Center

所以對Customizing serialization有點印象。

 

官方文檔描述十分簡略,只說了應該將類繼承IBsonSerializable 介面,然後實現四個方法。但是沒有樣本,完全不知道如何具體操作。

public class MyClass : IBsonSerializable { // implement Deserialize method // implement Serialize method }

 

好吧有google大神在。

stackoverflow是個好網站

http://stackoverflow.com/questions/7105274/storing-composite-nested-object-graph

 

3.解決:

 

第一部分:將對象變換成數字,節省名稱和空間消耗

 

        public UInt32 IntValue
        {
            get
            {
                var v1 = ((UInt32)WordTypes) << 24;
                var v2 = ((UInt32)Index) << 16;
                var v3 = ((UInt32)Length) << 8;                
                var v4 = (UInt32)0;//預留

                return v1 | v2 | v3 | v4;
            }
        }

        public void FromInt32(UInt32 value)
        {
            this.WordTypes = (WordTypes)(value >> 24);
            this.Index = (Int32)(value<<8 >> 24);
            this.Length = (Int32)(value << 16 >> 24);
        }

     

 
以上沒什麼好講的,無非左移右移,當然可能會出現資料類型溢出可能,如果有這種情況,換成Int64,或者適當修改。
說明一下,這個三級對象我不準備在mongoDB中檢索欄位,而是只用於儲存,至於檢索是變換成另外字串keyword的方式來檢索。
所以既然不需要檢索,屬性也就根本不需要有name,所以多個屬性可以位或成一個數值,存放到數組中。對象都省了。
 
第二部分
 public partial class Sentence : IBsonSerializable
    {

        public static int idSum;
        public bool GetDocumentId(out object id, out Type idNominalType, out IIdGenerator idGenerator)
        {
            id = this.Id = idSum++;
            idNominalType = typeof(int);
            idGenerator = null;
            return true;
        }

        public void Serialize(MongoDB.Bson.IO.BsonWriter bsonWriter, Type nominalType, IBsonSerializationOptions options)
        {
            bsonWriter.WriteStartDocument();
            bsonWriter.WriteInt32("_id", this.Id);  //10多個個位元組,如果用objectId
            bsonWriter.WriteString("value", this.Value);//名稱如果都改用幾個字母可以節省十幾個個位元組
            bsonWriter.WriteString("words", this.WordStr);
            bsonWriter.WriteBoolean("isConf", this.IsConflict);
            bsonWriter.WriteStartArray("c");

            foreach (var item in Chars)
            {
                BsonSerializer.Serialize(bsonWriter, item.Words.Select(v=>v.IntValue).ToList());  
            }        


            bsonWriter.WriteEndArray();            
            bsonWriter.WriteEndDocument();
        }

        public void SetDocumentId(object id)
        {
            throw new NotImplementedException();
        }

        public object Deserialize(MongoDB.Bson.IO.BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
        {
            //bsonReader.ReadStartDocument();
            //this.Id = bsonReader.ReadInt32(); 
            //var value=bsonReader.ReadString("v");
            //var wordStr=bsonReader.ReadString("w");
            //bsonReader.ReadStartArray();

            //var list = new List<List<Int32>>();
            //while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) 
            //{ 
            //    var element = BsonSerializer.Deserialize<List<Int32>>(bsonReader); 
            //    list.Add(element);             
            //}

            //bsonReader.ReadEndArray();
            //var isConflict=bsonReader.ReadBoolean("i");
            //bsonReader.ReadEndDocument();


            if (nominalType != typeof(Sentence))
                throw new ArgumentException("不能序列化,因為類型定義不一致");
            var doc = BsonDocument.ReadFrom(bsonReader);

            this.Id = (Int32)doc["_id"];
            this.Value = (string)doc["value"];
            this.WordStr = (string)doc["words"];
            this.IsConflict = (bool)doc["isConf"];
            var list = (BsonArray)doc["c"];

            this.Chars = new List<CharObj>();
            for (int i = 0; i < list.Count; i++)
            {
                var ch = new CharObj { Index = i, Sen = this, Words=new List<WordObj>() };
                this.Chars.Add(ch);

                var words = (BsonArray)list[i];

                foreach (Int32 item in words)
                {
                    var wordObj = new WordObj((UInt32)item);
                    wordObj.Sen = this;
                    ch.Words.Add(wordObj);
                }              
            }


            return this;
            //return new Sentence { Id=1,  IsConflict= true, Value="1", WordStr= "1"};
        }

    }
   

 

主要有幾個注意地方:

一個是Id的產生。我有點不明白為什麼id賦值函數要弄的那麼複雜的參數,但是這樣可以繞過ObjectID的 guid式的id,使用int可以節省一些空間。

當然,如果整體對象比較大,還是用objectID吧。完全沒必要用int,int也有很多問題,需要儲存最大值在另外的collection,沒法像ObjectId一樣跨多個Collection。所以mongoDB設計Id 用ObjectId而不是int,是非常有道理的。如果對象整體比較大,還是沒必要節省這十幾個位元組的消耗。

 

二是Serialize 方法的實現中,必須要以bsonWriter.WriteStartDocument()開始 bsonWriter.WriteEndDocument() 結束,切記,否則會報出一個沒法write的錯誤。

 

三是如何對二層的集合進行寫入,我原來是這樣寫的

             foreach (var item in Chars)
            {
                bsonWriter.WriteStartArray("words");
                foreach (var w in item.Words)
                    bsonWriter.WriteInt32((Int32)w.IntValue);
                bsonWriter.WriteEndArray();
            }          

 

但是mongoDB不支援這種嵌套式的持久化。

 

必須改成

             foreach (var item in Chars)
            {
                BsonSerializer.Serialize(bsonWriter, item.Words.Select(v=>v.IntValue).ToList());  
            }         
 
那個注意雖然 BsonSerializer.Serialize的參數是一個IEnumerable<T> 但是必須要ToList,否則不會儲存成功資料
 
第四,還原序列化的時候不能直接用start end方式,必然會報錯,只能先一次讀取,再取字典值
 
4.對比

 

 

新的bson格式的儲存比較緊湊了。

"_id" : ObjectId("4f4e2a02c992571e54c30465"),
"value" : "xxxxx",
"chars" : [{
"words" : [{
"index" : 0,
"length" : 2,
"wordTypes" : 0
}]
}, {
"words" : [{
"index" : 0,
"length" : 2,
"wordTypes" : 0
}, {
"index" : 1,
"length" : 2,
"wordTypes" : 0
}]
},

 

對比原來的,差距非常明顯。

 

用mongoVUE 查看平均 document大小,平均只有364byte了。原來可是嚇死人的4000

而合計Size也從200m下降到17m

 

而耗時  用我筆記本,耗時大概9秒鐘。原來40秒以上。而用台式機硬碟快,可以快幾倍,幾秒鐘內搞定。

 

 

5.其他

其實為什麼要實現自訂的持久化方法,一當然是效能十分的讓人憂慮。第二個則是對象關聯指標的重新綁定問題。

原來從資料庫讀取的資料,需要手工恢複相互關聯的指標,現在可以在還原序列化函數中直接完成這個操作。

也就是說,一旦查詢出來的對象,都已經和記憶體對像一摸一樣了。

好處是大大降低了程式的複雜度。

 

使用mongoDB資料對象,猶如記憶體對象一樣進行指標操作。然後自動永久化資料。

呃。我發覺愛上mongoDB了。雖然它還有不少缺點。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.