设计一套基于NHibernate二级缓存的MongoDB组件(上)

摘要:NHibernate Contrib 支持很多第三方的二级缓存,如SysCache,MemCache,Prevalence等等,但是没有MongoDB的,于是自己扩展了一个支持MongoDB的缓存组件(NHibernate.Caches.MongoDBCache.dll)。本篇先把组件的源代码开放出来。

 

一、背景

     在NHibernate的Contrib贡献项目官方网站(NHibernateContrib项目是由NHibernate开发团队或者终端用户根据需要自行编译并贡献的一系列的程序)中,拥有一个NHibernate.Caches的项目,里面包含汗多基于NHibernate二级缓存的组件,其中包括有:

NHibernate.Caches.MemCache:基于memcached分布式存储的缓存组件。这个大家都比较熟悉了就不多说了,详细可查阅相关信息。

NHibernate.Caches.Prevalence:基于Bamboo.Prevalence的缓存组件。它可产生一系列的缓存目录,通过缓存目录可以从文件中获取数据,并且在缓存目录中通过Snapshot,也就是快照,可以进行断点保存。详细介绍请看我的文章:(在Spring.Net中对于NHibernate.Caches.Prevalence的使用

NHibernate.Caches.SharedCache:基于MergeSystem.Indexus.WinServiceCommon、MergeSystem.Indexus.WinService和MergeSystem.Indexus.Notify的分布式存储的缓存组件。用于在动态WEB或Win应用程序中减少数据库的负责,提高访问速度。

NHibernate.Caches.SysCache:我们通常DotNet上所使用的System.Web.Caching.Cache。

NHibernate.Caches.SysCache2:同上。不同的是增加了对于SQL2005的缓存依赖的支持。

NHibernate.Caches.Velocity:基于微软推出的分布式缓存Velocity组件。跟memcached一样,“Velocity”维护一张大的哈希表,这张表可以跨越多个服务器,你可以通过添加或者减少服务器来平衡系统压力。

 

二、什么是MongoDB?

      MongoDB是一个基于分布式文档存储的数据库。旨在为WEB应用提供可护展的高性能数据存储解决方案。它是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。他支持的数据结构非常松散,是类似json的bjson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。 它的特点是高性能、易部署、易使用,存储数据非常方便。

MongoDB官方服务端下载地址:http://www.mongodb.org/downloads

MongoDB官方客户端(.NET)下载地址:https://github.com/samus/mongodb-csharp

 

三、准备工作

服务器端下载下来后,首先要安装MongoDB,大家可以参考下这篇文章:http://www.cnblogs.com/mamboer/archive/2010/03/05/1679292.html

在你开发之前必须先吧MongoDB的服务或者控制台启动。这里我采用启动控制台。

image

从图中看出,MongoDB采用的默认端口是27017,并且在我安装的时候,将MongoDB的数据库目录配置在:C:\data\db上。

      现在开始,我要增加一个支持MongoDB的缓存组件,那么首先要先了解它们二级缓存流程的一些机制,本篇先不具体谈它的原理(会在下篇具体描述),先谈下它是如何实现的,要研究如何实现其实很简单,依葫芦画瓢,去看人家写的代码。

 

四、分析与实现

1. 在Spring.NET关于NHibernate的配置中,可以启用二级缓存其中有个配置节点是:

< entry  key ="cache.provider_class"  value ="NHibernate.Cache.HashtableCacheProvider" />

HashtableCacheProvider是NHibernate二级缓存中自带的默认的缓存提供程序。而HashtableCacheProvider继承的是ICacheProvider接口,于是要创建一个支持MongoDB的缓冲提供程序,就必须继承它。

 

2. 创建一个MongoDBCacheProvider类:

ExpandedBlockStart.gif 代码
     ///   <summary>
    
///  MongoDB缓存提供程序
    
///   </summary>
     public   class  MongoDBCacheProvider : ICacheProvider
    {
        
private   static   readonly  ILog log  =  LogManager.GetLogger( typeof (MongoDBCacheProvider));

        
static  MongoDBCacheProvider()
        {

        }

        
public  ICache BuildCache( string  regionName, IDictionary < string string >  properties)
        {
            
if  (regionName  ==   null )
            {
                regionName 
=   string .Empty;
            }
            
if  (properties  ==   null )
            {
                properties 
=   new  Dictionary < string string > ();
            }
            
if  (log.IsDebugEnabled)
            {

            }

            
return   new  MongoDBCache(regionName, properties);
        }

        
public   long  NextTimestamp()
        {
            
return  Timestamper.Next();
        }

        
public   void  Start(IDictionary < string string >  properties)
        {
        }

        
public   void  Stop()
        {
        }
    }

这样就实现了一个初步的MongoDB缓存提供程序的构架。注意到BuildCache方法返回的是一个ICache对象。这里就必须实现一个继承ICache接口的MongoDB缓存对象。

 

3. 看下ICache都定义了哪些接口方法和属性:

ExpandedBlockStart.gif 代码
public   interface  ICache 


    
void  Clear(); 
    
void  Destroy(); 
    
object  Get( object  key); 
    
void  Lock( object  key); 
    
long  NextTimestamp(); 
    
void  Put( object  key,  object  value); 
    
void  Remove( object  key); 
    
void  Unlock( object  key); 

    
string  RegionName {  get ; } 
    
int  Timeout {  get ; } 
}

从字面上解释,应该大家都能够明白的:Clear清空缓存,Destroy和Clear类似,但是具体问题具体分析,Get取缓存,Lock锁定缓存,在ReadWrite模式的缓存上需要使用到,NextTimestamp下一时间段的时间戳,Put设置缓存,Remove清除指定的缓存数据,Unlock解除锁定,同样在ReadWrite模式的缓存上需要使用,RegionName区域名称,Timeout缓存过期时间。

 

4. 创建一个MongoDBCache的缓存类:

在它的构造函数中的代码:

ExpandedBlockStart.gif 代码
         public  MongoDBCache( string  regionName, IDictionary < string string >  properties)
        {
            _regionName 
=  regionName;

            
if  (properties  !=   null )
            {
                
string  dbName  =   string .Empty;
                
if  (properties.TryGetValue( " mongodb.dasebaseName " out  dbName))
                {
                    
if  ( ! string .IsNullOrEmpty(dbName))
                    {
                        _dbName 
=  dbName;
                    }
                }

                
string  connectionString  =   string .Empty;
                
if  (properties.TryGetValue( " mongodb.connectionString " out  connectionString))
                {
                    
if  ( ! string .IsNullOrEmpty(connectionString))
                    {
                        _connectionString 
=  connectionString;
                    }
                }

                
string  pattern  =   string .Empty;
                
if  (properties.TryGetValue( " mongodb.pattern " out  pattern))
                {
                    
if  ( ! string .IsNullOrEmpty(pattern))
                    {
                        _pattern 
=  pattern;
                    }
                }

                
string  regionPrefix  =   string .Empty;
                
if  (properties.TryGetValue( " regionPrefix " out  regionPrefix))
                {
                    
if  ( ! string .IsNullOrEmpty(regionPrefix))
                    {
                        _regionPrefix 
=  regionPrefix;
                    }
                }
            }

            mongo 
=   new  Mongo(_connectionString);

            
//  连接
            mongo.Connect();

            
//  获取Mongo数据库实体
            db  =  mongo[_dbName];
        }

其中可以看出这里需要连接mongo的对象,并且指定它的数据库。

而在它的析构函数中:

ExpandedBlockStart.gif 代码
~ MongoDBCache() 

    Dispose(); 


///   <summary>  
///  释放资源 
///   </summary>  
public   void  Dispose() 

    
//  关闭连接 
    mongo.Disconnect(); 
    
//  释放mongo资源 
    mongo.Dispose(); 
}

必须关闭mongo的连接,并且释放mongo资源。

对于存储缓存数据(存在Mongo数据库的表中):

ExpandedBlockStart.gif 设置缓存数据Put
         public   void  Put( object  key,  object  value)
        {
            
if  (key  ==   null )
            {
                
throw   new  ArgumentNullException( " key " " null key not allowed " );
            }
            
if  (value  ==   null )
            {
                
throw   new  ArgumentNullException( " value " " null value not allowed " );
            }
            
if  (log.IsDebugEnabled)
            {
                log.DebugFormat(
" setting value for item {0} " , key);
            }

            
string  hashKey  =  GetAlternateKeyHash(key);

            GenerateTableName(key);

            Console.WriteLine(
string .Format( " Put------Key:{0}, Value:{1} " , hashKey, value.ToString()));

            IMongoCollection
< Document >  table  =  db.GetCollection < Document > (TableName);

            IDictionary
< string object >  dict  =   new  Dictionary < string object > ();
            dict.Add(
" Key " , hashKey);

            Document query 
=   new  Document(dict);

            
//  查询
            Document document  =  table.FindOne(query);

            
try
            {
                
if  (document  ==   null )
                {
                    IDictionary
< string object >  newDict  =   new  Dictionary < string object > ();
                    newDict.Add(
" Value " , SerializeHelper.BinarySerialize(value));
                    newDict.Add(
" Key " , hashKey);
                    newDict.Add(
" Type " , value.GetType().Name);
                    newDict.Add(
" Date " , DateTime.Now.ToString());

                    document 
=   new  Document(newDict);
                }
                
else
                {
                    document[
" Value " =  SerializeHelper.BinarySerialize(value);
                    document[
" Type " =  value.GetType().Name;
                    document[
" Date " =  DateTime.Now.ToString();
                }

                
//  保存Document
                table.Save(document);
            }
            
catch
            {
            }
            
finally
            {
            }
        }

这里会将value对象序列化为字节数组,有人会问为什么不直接存储对象呢,还需要序列化,这是由于它的存储的数据结构决定的,它最后在数据库中形成的结果为一个BSON结构;还有人会问可以把它序列化为JSON字符串吗,我也做过尝试,但是后来发现value实际上的类型是CacheItem或者CacheEntity,它们都没有无参的构造函数,所以无法反序列化。因此,这里我采用了字节转换的方式。

从代码中,可以看到document包含Key,Value,Type,Date(非必须的)的字段,其中Type在获取缓存数据(Get)的时候非常有用。

对于获取数据:

ExpandedBlockStart.gif 获取缓存数据Get
         public   object  Get( object  key)
        {
            
string  hashKey  =  GetAlternateKeyHash(key);

            GenerateTableName(key);

            Console.WriteLine(
string .Format( " Get------Key:{0} " , hashKey));

            IMongoCollection
< Document >  table  =  db.GetCollection < Document > (TableName);

            IDictionary
< string object >  dict  =   new  Dictionary < string object > ();
            dict.Add(
" Key " , hashKey);

            Document query 
=   new  Document(dict);

            
//  查询
            Document document  =  table.FindOne(query);

            
if  (document  !=   null )
            {
                
try
                {
                    
byte []  bytes  =  ((MongoDB.Binary)document[ " Value " ]).Bytes;

                    
#region  反序列化字节数组

                    
if  ( string .Equals(document[ " Type " ].ToString(),  typeof (CacheEntry).Name, StringComparison.InvariantCultureIgnoreCase))
                    {
                        
return  SerializeHelper.BinaryDeSerialize < CacheEntry > (bytes);
                    }
                    
else   if  ( string .Equals(document[ " Type " ].ToString(),  typeof (CachedItem).Name, StringComparison.InvariantCultureIgnoreCase))
                    {
                        
return  SerializeHelper.BinaryDeSerialize < CachedItem > (bytes);
                    }
                    
else   if  ( string .Equals(document[ " Type " ].ToString(),  typeof (List < Object > ).Name, StringComparison.InvariantCultureIgnoreCase))
                    {
                        
return  SerializeHelper.BinaryDeSerialize < List < Object >> (bytes);
                    }
                    
else   if  ( string .Equals(document[ " Type " ].ToString(),  typeof (Int64).Name, StringComparison.InvariantCultureIgnoreCase))
                    {
                        
return  SerializeHelper.BinaryDeSerialize < Int64 > (bytes);
                    }
                    
else   if  ( string .Equals(document[ " Type " ].ToString(),  typeof (CacheLock).Name, StringComparison.InvariantCultureIgnoreCase))
                    {
                        
return  SerializeHelper.BinaryDeSerialize < CacheLock > (bytes);
                    }
                    
else
                    {
                        
return   null ;
                    }

                    
#endregion
                }
                
catch
                {
                    
return   null ;
                }
            }
            
return   null ;
        }

其中Document document = table.FindOne(query);是从表中根据指定的Document查询数据。并且对于字节数据Value字段,必须进行字节反序列化。

在Spring.NET对于NH的配置节点中可以这样子写:

ExpandedBlockStart.gif 代码
<!--  MongoDB缓存机制  -->  
< entry  key ="cache.provider_class"  value ="NHibernate.Caches.MongoDBCache.MongoDBCacheProvider, NHibernate.Caches.MongoDBCache"   />  
< entry  key ="mongodb.dasebaseName"  value ="xinogxt"   />  
< entry  key ="mongodb.connectionString"  value ="servers=127.0.0.1:27017"   />  
< entry  key ="mongodb.pattern"  value ="^TestWebServer\.Model\..+?" />

其中mongodb.dasebaseName是给MongoDB配置的数据库名称;mongodb.connectionString是MongoDB服务的连接字符串;mongodb.pattern是为了作为表名称的匹配正则表达式,可以看下这段代码:

ExpandedBlockStart.gif 代码
///   <summary>  
///  生成表格名称 
///   </summary>  
///   <param name="key"></param>  
private   void  GenerateTableName( object  key) 

    
if  (key  is  CacheKey) 
    { 
        CacheKey cacheKey 
=  (CacheKey)key; 

        
//  判断是否匹配正则表达式 
         if  (Regex.IsMatch(cacheKey.EntityOrRoleName, _pattern)) 
        { 
            _tableName 
=  cacheKey.EntityOrRoleName.Replace( " . " " _ " ); 
        } 
    } 
}

它是通过CacheKey的EntityOrRoleName属性,进行筛选,比如:这里的EntityOrRoleName为”“TestWebServer.Model.TblEnterprise”的字符串(这是一个NH自动生成的实体类),我给它的正则表达式为“^TestWebServer\.Model\..+?”,那么它匹配了,我就取它的这个字符串为表名称,最后的表名为:“TestWebServer_Model_TblEnterprise”。这样我缓存每一个实体,都能够自动创建相应的一个Mongo表。

 

5. 看下运行的结果:

测试代码如下:

[Test] 
public   void  EnterpriseDaoTest6() 

    IEnterpriseDao dao 
=  (IEnterpriseDao)applicationContext.GetObject( " EnterpriseDao " ); 
    ITblEnterprise enterprise 
=  dao.GetInfo( 1 );

    …

}

第一次执行:

image 

第一次的时候,执行了数据库的SELECT的SQL语句。

我查看本地目录以及用MongoVUE客户端工具查看了下Mongo数据库:

image

image

缓存数据已经存在目录(数据库)中。

第二次执行:

image

发现这里没有执行SQL。

说明MongoDB缓存成功。

 

6. 通过对对于NHibernate二级缓存机制的理解,我们完全可以扩展属于我们自己的缓存组件。不仅仅是作为MongoDB为载体的缓存实现。

因此,在下一篇文章中,我将重点介绍关于NHibernate二级缓存机制的原理,并且继续深入探讨MongoDB缓存组件的相关原理。

 

NHibernate.Caches.MongoDBCache.dll项目源代码下载:NHibernate.Caches.MongoDBCache.rar

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值