设为首页 收藏本站
查看: 1608|回复: 0

[经验分享] MongoDB的自定义序列化(Customizing serialization)

[复制链接]

尚未签到

发表于 2015-7-6 11:35:26 | 显示全部楼层 |阅读模式
  我最近一直在研究MongoDB,有些小心得。恰好发现原来博客园支持Live writer啊
  兴奋异常,终于多年以后重回这里。以前一直用liver writer写 myspace和 wordpress
  但是前者完了,后者FQ很烦。
  ====================================================
  首先推荐一个MongoDB的查询分析器
DSC0000.png
  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);
        }
  
   以上没什么好讲的,无非左移右移,当然可能会出现数据类型溢出可能,如果有这种情况,换成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();
            //while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
            //{
            //    var element = BsonSerializer.Deserialize(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();
            for (int i = 0; i < list.Count; i++)
            {
                var ch = new CharObj { Index = i, Sen = this, Words=new List() };
                this.Chars.Add(ch);
                var words = (BsonArray)list;
                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 但是必须要ToList,否则不会保存成功数据 第四,反序列化的时候不能直接用start end方式,必然会报错,只能先一次读取,再取字典值 4.对比
  
DSC0001.png
  
  新的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
}]
},  
  对比原来的,差距非常明显。
DSC0002.png
  
  用mongoVUE 查看平均 document大小,平均只有364byte了。原来可是吓死人的4000
  而合计Size也从200m下降到17m
  
  而耗时 DSC0003.png   用我笔记本,耗时大概9秒钟。原来40秒以上。而用台式机硬盘快,可以快几倍,几秒钟内搞定。
  
  
  5.其他
  其实为什么要实现自定义的持久化方法,一当然是性能十分的让人忧虑。第二个则是对象关联指针的重新绑定问题。
  原来从数据库读取的数据,需要手工恢复相互关联的指针,现在可以在反序列化函数中直接完成这个操作。
  也就是说,一旦查询出来的对象,都已经和内存对像一摸一样了。
  好处是大大降低了程序的复杂度。
  
  使用mongoDB数据对象,犹如内存对象一样进行指针操作。然后自动永久化数据。
  呃。我发觉爱上mongoDB了。虽然它还有不少缺点。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-83794-1-1.html 上篇帖子: mongodb查询(原创) 下篇帖子: MongoDB中常用的find
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表