zoukankan      html  css  js  c++  java
  • 设计一套基于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类:

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

            
    static MongoDBCacheProvider()
            {

            }

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

                }

                
    return new MongoDBCache(regionName, properties);
            }

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

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

            
    public void Stop()
            {
            }
        }

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

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

    代码
    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的缓存类:

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

    代码
            public MongoDBCache(string regionName, IDictionary<stringstring> 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的对象,并且指定它的数据库。

    而在它的析构函数中:

    代码
    ~MongoDBCache() 

        Dispose(); 


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

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

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

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

    设置缓存数据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
    <stringobject> dict = new Dictionary<stringobject>();
                dict.Add(
    "Key", hashKey);

                Document query 
    = new Document(dict);

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

                
    try
                {
                    
    if (document == null)
                    {
                        IDictionary
    <stringobject> newDict = new Dictionary<stringobject>();
                        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)的时候非常有用。

    对于获取数据:

    获取缓存数据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
    <stringobject> dict = new Dictionary<stringobject>();
                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的配置节点中可以这样子写:

    代码
    <!-- 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是为了作为表名称的匹配正则表达式,可以看下这段代码:

    代码
    /// <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

  • 相关阅读:
    时钟展频技术能有效降低EMI,深入讲解展频发生器!
    24:购物单
    22:按照字典输出字符串
    21:句子逆序
    20:字符反转
    19:ReverseNumber数字颠倒
    DNS原理及其解析过程
    18:字符个数统计
    17:不重复整数提取NoRepeatNum
    \s+ split替换
  • 原文地址:https://www.cnblogs.com/liping13599168/p/1922542.html
Copyright © 2011-2022 走看看