随笔-197  评论-2252  文章-1  trackbacks-63

Mongodb官方驱动的进一步包装

 

    最近有一个需求,对数据的实时性要求比较高,之前寻找过一些内存数据库,首先将收费的产品先排除掉,然后再排除一些嵌入式产品,最终留下两个产品:
    1:Mysql内存引擎;
    2:基于内存文件映射的文档数据库Mongodb;


    针对以上两种产品,mysql内存引擎有如下缺点是我们放弃的理由:
    1:数据量比较大的情况,比之innodb引擎优势并不明显,数据小的时候采用B-Tree索引结构性能还是可以接受的。
    2:内存引擎对于持久化是非常吃力的,如果大量数据操作均在内存引擎中完成,那么就需要我们来做数据的持久化,无论是复制订阅方式还是程序方式均存在比较大的困难。

    3:内存引擎所占用的数据空间比较大,特别是默认的hash结构,同数据量的数据是sql server的6倍,B-Tree结构未做具体比较。


    选择Mongodb的理由:
    1:自身具备数据的持久化;
    2:对于数据的插入性能优越,特别是采用不返回值的模式,理论上来讲是一种异步操作;
    3:由于文档存储是k-value方式,所以对于单条记录的查询操作性能不用考虑;

    4:对于水平扩展,数据的主从备份均非常容易。


    使用Mongodb过程中遇到过的问题:
    1:由于存储的内容完全由客户端决定,于是对于一些对字段做更新的操作,比如sql 中的set count=count+1;存在一个并发更新问题,当两个线程同时需要对count字段加1时,两个线程同时查询到的count内容为1,当线程A完成加1后,数据库中实际已经变成2,此时线程B对count进行加1,此时也是2,于时原来是3的内容,最终会变成2。主要原因就在于更新字段时并不会在服务端进行,字段的内容完全是由客户端决定,即更新时并不会像sql server一样,在服务端变量基础上再做操作。


    解决方案:对记录增加自定义的锁标识,尝试在记录上增加锁,如果加锁成功,然后再查询,如果锁标识证明是当前线程才进行更新,否则循环尝试加锁。这里有发生死锁的可能,为此在加锁标记时,可以增加一个锁时间,当超过锁时间后,自动解锁,这样可以避免死锁。


    2:数据的插入,主要有两种模式,一种是不返回处理结果,一种是返回处理结果,如果我们不太关心返回结果,那么可以获得最高插入性能,对于一些关注处理结果的业务场景,就需要采取返回结果方式。


    3:最好自定义主键,不采用默认的_id值,我们处理一条学生记录,学生记录是唯一的,比如学生ID,如果我们采用默认_id值,那么在插入数据时也会存在并发问题:两个线程同时处理学生A,当时查询时发现数据库中没有学生A的记录,于时就进行插入,由于默认的_id值是自动生成,类似guid或者是自增id,说的通俗点就是会生成一个不重复的key,此时就会插入学生A记录两次,如果我们选用学生ID做为主键,就可以避免此种情况,当第二次尝试插入时,系统会抛出主键重复的异常。
   
    为什么需要对mongodb驱动重新封装?
    其实无论是samus驱动还是官方驱动,其实功能都各有秋千,samus驱动对数据操作进行了Linq封装,即我们在操作List时,完全可以采用类似Linq一样的语法,这样可以使我们的学习成本降低,官方驱动的特点是针对数据处理有返回值。我们的需求是即需要操作返回值也需要Linq封装,于时我找到了老赵写的easyMongo,但在它的基本上做了一小部分取舍,取舍内容如下:
   
    1:去掉了如下逻辑,原意是将大多数操作均放在同一连接中处理,但不满足我们的需求;我们直接对外提供一次性执行方法 

    

InsertOnSubmit,UpdateOnSubmit,DeleteOnSubmit
     
public void SubmitChanges()
        {
            
using (this.Database.Server.RequestStart(this.Database))
            {
                
this.DeleteEntities();
                
this.UpdateEntites();
                
this.InsertEntities();
            }
        }

 

    2:对上面所提到的insert,update ,delete要求有返回值,对于insert提供两种模式,一类是需要返回值一类则不需要。特别是像更新和删除,我们是非常有必要知道它的处理结果,如果不关心结果,我认为可以是一些数据准确性要求不高的场景。

   

     public EInsertstatus InsertOnSubmit(TEntity entity)
        {
            
if (entity == nullthrow new ArgumentNullException();
            
return this.InsertEntities(entity,ESafeMode.False);
        }
        
public EInsertstatus InsertOnSubmit(TEntity entity, ESafeMode safeMode)
        {
            
if (entity == nullthrow new ArgumentNullException();
            
return this.InsertEntities(entity, safeMode);
        }
        
public void InsertBatchOnSubmit(List<TEntity> entity)
        {
            
if (entity == nullthrow new ArgumentNullException();
            
this.InsertBatchEntities(entity);
        }
        
public bool UpdateOnSubmit(TEntity entity)
        {
            
if (entity == nullthrow new ArgumentNullException();
            
return this.UpdateEntites(entity);
        }
        
public bool DeleteOnSubmit(TEntity entity)
        {
            
if (entity == nullthrow new ArgumentNullException();

            
return this.DeleteEntities(entity);
        }


        
     3:对查询的修改,在将实体映射为mongo文档时,需要对我们自定义的实体提供一个Map,这个Map主要是为了标识哪个是主键,哪些字段是可以被Linq识别的字段,比如我们要按学生ID查询,就需要在Map中增加此字段,示例如下:
        

public class AggregateCompanyRecruitStudentInfoMap : EntityMap<AggregateCompanyRecruitStudentInfo>
    {
        
public AggregateCompanyRecruitStudentInfoMap()
        {
            Collection(
"AggregateCompanyRecruitStudentInfo");
            Property(n 
=> n.OrganizationID).Identity();
            Property(n 
=> n.LockTime);
            Property(n 
=> n.LockFiled);

        }
    }

 

    但源码中的查询,如果没有提供查询表达式,则默认只返回Map中提到的字段,对其它实体字段则不处理,为此修改如下:即没有查询表达式的情况返回完整文档。
       

 FieldsDocument fieldsDoc = null;
            
if (null != selector)
            {
                fieldsDoc 
= mapper.GetFields(selector);
            }

 

    4:去掉原有复杂的更新逻辑,删除对我们没用的状态跟踪,要的就是简单直接,过于复杂的逻辑不利于开发。
       
    Mymongo的一个简化结构图分享给大家:


       
       说明:最后欢迎大家交流mongodb,文中如有不对的地方,希望批评指正,大家共同进步。

 

标签: mongodb
posted on 2011-08-27 08:32 min.jiang 阅读(1897) 评论(13) 编辑 收藏

评论:
#1楼 2011-08-27 09:18 | Kain      
对于mongodb就有很好的支持:
MongoDB.Driver.Builders.Update.Inc(field, val);
官方的详细文档估计没看

 回复 引用 查看   
#2楼 2011-08-27 09:33 | pulihe      
Good,现在程序不少都有自集成嵌入数据库的需要;不知博主是否可以谈谈这方面?
 回复 引用 查看   
#3楼[楼主] 2011-08-27 09:36 | 姜敏      
@Kain
嗯,您说的没错,下次找一时间试试

 回复 引用 查看   
#4楼 2011-09-01 19:08 | iammutex      
上面说的有一些问题:

1.并发加1的问题,MongoDB的单个文档修改都是原子性的,要做加1操作,直接使用$inc的方式进行修改就行了。

2.对于设定自定义主键防止重复行的说法。MongoDB和MySQL一样,都是支持唯一索引的,只要给不希望重复的列加上唯一索引,就可以实现重复报错了。当然,如果用自己的字段必须保证唯一,那么用字段做主键减少内存占用也是可以的。也是官方推荐的做法。

3.MongoDB在Replication上支持主从和Replica Sets两种结构,主要解决的是数据安全性备份的问题。水平扩展性问题主要通过MongoDB提供的auto sharding机制来解决。

 回复 引用 查看   
#5楼[楼主] 2011-09-02 09:20 | 姜敏      
@iammutex
1.并发加1的问题,MongoDB的单个文档修改都是原子性的,要做加1操作,直接使用$inc的方式进行修改就行了。

这个我一直没有成功过,您可能作下实验,100个线程并发,实现将一个字段从0变成100。

 回复 引用 查看   
#6楼 2011-09-02 14:21 | iammutex      
@姜敏
我做过很多次并发实验,都没有发生过这种事。我觉得你可以看一下是不是你的测试工具有问题,比如是否运行的次数本来就不准确。比如用apache ab这种工具,次数上都是有误差的。也可以从MongoDB的serverStatus里看一下具体的操作次数。

 回复 引用 查看   
#7楼 2011-09-03 11:46 | 小徐的博客      
博主可不可以把你改的Mongo帮助类的源码发一份给我不习一下。谢谢了。
8672429@gmail.com

 回复 引用 查看   
#8楼[楼主] 2011-09-04 23:09 | 姜敏      
@iammutex
给您发信息了,希望加你为好友,共同学习

 回复 引用 查看   
#9楼[楼主] 2011-09-04 23:10 | 姜敏      
@小徐的博客
没问题,等哪天再整理下再发给您参考

 回复 引用 查看   
#10楼 2011-09-11 00:25 | 龙卷风go      
兄弟,你的文章写得很好!值得多多学习!
我目前正在学习mongodb,希望能加我为好友,一起交流。
QQ:286516717

 回复 引用 查看   
#11楼 2011-11-14 15:27 | 不若相忘于江湖      
2:数据的插入,主要有两种模式,一种是不返回处理结果,一种是返回处理结果,如果我们不太关心返回结果,那么可以获得最高插入性能,对于一些关注处理结果的业务场景,就需要采取返回结果方式

请问楼主用的驱动是哪个版本,为什么我的Insert方法没有返回值

 回复 引用 查看   
#12楼[楼主] 2011-11-23 10:22 | 姜敏      
@不若相忘于江湖
就是官方的驱动啊

 回复 引用 查看   
#13楼 2012-02-09 08:34 | Virus-BeautyCode      
@不若相忘于江湖

var rst = collection.Insert<Token>(Token.GetAnonymous("123"),SafeMode.True );

请注意后面的参数SafeMode,建议买一本《MongoDB权威指南》,然后通读一遍,绝对超值。
var rst = collection.Save<Token>(Token.GetAnonymous("123"),SafeMode.True );

使用save还可以看到effectedrows的数量,使用insert这个数字是0.
在update的时候还需要注意UpdateFlags的选择。参考官方文档。

 回复 引用 查看   
min的个人网站终于创建起来了
昵称:min.jiang
园龄:5年6个月
粉丝:144
关注:8
<2011年8月>
31123456
78910111213
14151617181920
21222324252627
28293031123
45678910

常用链接

最新随笔

随笔分类

随笔档案

Follow Me

博客园友情链接

拳皇比赛视频

积分与排名

  • 积分 - 493980
  • 排名 - 124

最新评论

阅读排行榜

评论排行榜

推荐排行榜