设计一套基于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的服務或者控制臺啟動。這里我采用啟動控制臺。
從圖中看出,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<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都定義了哪些接口方法和屬性:
代碼 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<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的對象,并且指定它的數據庫。
而在它的析構函數中:
代碼 ~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<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)的時候非常有用。
對于獲取數據:
獲取緩存數據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的配置節點中可以這樣子寫:
代碼 <!--?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);
????…
}
第一次執行:
?
第一次的時候,執行了數據庫的SELECT的SQL語句。
我查看本地目錄以及用MongoVUE客戶端工具查看了下Mongo數據庫:
緩存數據已經存在目錄(數據庫)中。
第二次執行:
發現這里沒有執行SQL。
說明MongoDB緩存成功。
?
6. 通過對對于NHibernate二級緩存機制的理解,我們完全可以擴展屬于我們自己的緩存組件。不僅僅是作為MongoDB為載體的緩存實現。
因此,在下一篇文章中,我將重點介紹關于NHibernate二級緩存機制的原理,并且繼續深入探討MongoDB緩存組件的相關原理。
?
NHibernate.Caches.MongoDBCache.dll項目源代碼下載:NHibernate.Caches.MongoDBCache.rar
總結
以上是生活随笔為你收集整理的设计一套基于NHibernate二级缓存的MongoDB组件(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: asp.net WebForm页面间传值
- 下一篇: 事业