设计一套基于NHibernate二级缓存的MongoDB组件(上)
摘要:NHibernate Contrib 支持很多第三方的二級緩存,如SysCache,MemCache,Prevalence等等,但是沒有MongoDB的,于是自己擴(kuò)展了一個(gè)支持MongoDB的緩存組件(NHibernate.Caches.MongoDBCache.dll)。本篇先把組件的源代碼開放出來。
?
一、背景
???? 在NHibernate的Contrib貢獻(xiàn)項(xiàng)目官方網(wǎng)站(NHibernateContrib項(xiàng)目是由NHibernate開發(fā)團(tuán)隊(duì)或者終端用戶根據(jù)需要自行編譯并貢獻(xiàn)的一系列的程序)中,擁有一個(gè)NHibernate.Caches的項(xiàng)目,里面包含汗多基于NHibernate二級緩存的組件,其中包括有:
NHibernate.Caches.MemCache:基于memcached分布式存儲的緩存組件。這個(gè)大家都比較熟悉了就不多說了,詳細(xì)可查閱相關(guān)信息。
NHibernate.Caches.Prevalence:基于Bamboo.Prevalence的緩存組件。它可產(chǎn)生一系列的緩存目錄,通過緩存目錄可以從文件中獲取數(shù)據(jù),并且在緩存目錄中通過Snapshot,也就是快照,可以進(jìn)行斷點(diǎn)保存。詳細(xì)介紹請看我的文章:(在Spring.Net中對于NHibernate.Caches.Prevalence的使用)
NHibernate.Caches.SharedCache:基于MergeSystem.Indexus.WinServiceCommon、MergeSystem.Indexus.WinService和MergeSystem.Indexus.Notify的分布式存儲的緩存組件。用于在動態(tài)WEB或Win應(yīng)用程序中減少數(shù)據(jù)庫的負(fù)責(zé),提高訪問速度。
NHibernate.Caches.SysCache:我們通常DotNet上所使用的System.Web.Caching.Cache。
NHibernate.Caches.SysCache2:同上。不同的是增加了對于SQL2005的緩存依賴的支持。
NHibernate.Caches.Velocity:基于微軟推出的分布式緩存Velocity組件。跟memcached一樣,“Velocity”維護(hù)一張大的哈希表,這張表可以跨越多個(gè)服務(wù)器,你可以通過添加或者減少服務(wù)器來平衡系統(tǒng)壓力。
?
二、什么是MongoDB?
????? MongoDB是一個(gè)基于分布式文檔存儲的數(shù)據(jù)庫。旨在為WEB應(yīng)用提供可護(hù)展的高性能數(shù)據(jù)存儲解決方案。它是一個(gè)介于關(guān)系數(shù)據(jù)庫和非關(guān)系數(shù)據(jù)庫之間的產(chǎn)品,是非關(guān)系數(shù)據(jù)庫當(dāng)中功能最豐富,最像關(guān)系數(shù)據(jù)庫的。他支持的數(shù)據(jù)結(jié)構(gòu)非常松散,是類似json的bjson格式,因此可以存儲比較復(fù)雜的數(shù)據(jù)類型。Mongo最大的特點(diǎn)是他支持的查詢語言非常強(qiáng)大,其語法有點(diǎn)類似于面向?qū)ο蟮牟樵冋Z言,幾乎可以實(shí)現(xiàn)類似關(guān)系數(shù)據(jù)庫單表查詢的絕大部分功能,而且還支持對數(shù)據(jù)建立索引。 它的特點(diǎn)是高性能、易部署、易使用,存儲數(shù)據(jù)非常方便。
MongoDB官方服務(wù)端下載地址:http://www.mongodb.org/downloads
MongoDB官方客戶端(.NET)下載地址:https://github.com/samus/mongodb-csharp
?
三、準(zhǔn)備工作
服務(wù)器端下載下來后,首先要安裝MongoDB,大家可以參考下這篇文章:http://www.cnblogs.com/mamboer/archive/2010/03/05/1679292.html
在你開發(fā)之前必須先吧MongoDB的服務(wù)或者控制臺啟動。這里我采用啟動控制臺。
從圖中看出,MongoDB采用的默認(rèn)端口是27017,并且在我安裝的時(shí)候,將MongoDB的數(shù)據(jù)庫目錄配置在:C:\data\db上。
????? 現(xiàn)在開始,我要增加一個(gè)支持MongoDB的緩存組件,那么首先要先了解它們二級緩存流程的一些機(jī)制,本篇先不具體談它的原理(會在下篇具體描述),先談下它是如何實(shí)現(xiàn)的,要研究如何實(shí)現(xiàn)其實(shí)很簡單,依葫蘆畫瓢,去看人家寫的代碼。
?
四、分析與實(shí)現(xiàn)
1. 在Spring.NET關(guān)于NHibernate的配置中,可以啟用二級緩存其中有個(gè)配置節(jié)點(diǎn)是:
<entry?key="cache.provider_class"?value="NHibernate.Cache.HashtableCacheProvider"/>HashtableCacheProvider是NHibernate二級緩存中自帶的默認(rèn)的緩存提供程序。而HashtableCacheProvider繼承的是ICacheProvider接口,于是要創(chuàng)建一個(gè)支持MongoDB的緩沖提供程序,就必須繼承它。
?
2. 創(chuàng)建一個(gè)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()
????????{
????????}
????}
這樣就實(shí)現(xiàn)了一個(gè)初步的MongoDB緩存提供程序的構(gòu)架。注意到BuildCache方法返回的是一個(gè)ICache對象。這里就必須實(shí)現(xiàn)一個(gè)繼承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;?}?
}
從字面上解釋,應(yīng)該大家都能夠明白的:Clear清空緩存,Destroy和Clear類似,但是具體問題具體分析,Get取緩存,Lock鎖定緩存,在ReadWrite模式的緩存上需要使用到,NextTimestamp下一時(shí)間段的時(shí)間戳,Put設(shè)置緩存,Remove清除指定的緩存數(shù)據(jù),Unlock解除鎖定,同樣在ReadWrite模式的緩存上需要使用,RegionName區(qū)域名稱,Timeout緩存過期時(shí)間。
?
4. 創(chuàng)建一個(gè)MongoDBCache的緩存類:
在它的構(gòu)造函數(shù)中的代碼:
代碼 ????????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數(shù)據(jù)庫實(shí)體
????????????db?=?mongo[_dbName];
????????}
其中可以看出這里需要連接mongo的對象,并且指定它的數(shù)據(jù)庫。
而在它的析構(gòu)函數(shù)中:
代碼 ~MongoDBCache()?{?
????Dispose();?
}?
///?<summary>?
///?釋放資源?
///?</summary>?
public?void?Dispose()?
{?
????//?關(guān)閉連接?
????mongo.Disconnect();?
????//?釋放mongo資源?
????mongo.Dispose();?
}
必須關(guān)閉mongo的連接,并且釋放mongo資源。
對于存儲緩存數(shù)據(jù)(存在Mongo數(shù)據(jù)庫的表中):
設(shè)置緩存數(shù)據(jù)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對象序列化為字節(jié)數(shù)組,有人會問為什么不直接存儲對象呢,還需要序列化,這是由于它的存儲的數(shù)據(jù)結(jié)構(gòu)決定的,它最后在數(shù)據(jù)庫中形成的結(jié)果為一個(gè)BSON結(jié)構(gòu);還有人會問可以把它序列化為JSON字符串嗎,我也做過嘗試,但是后來發(fā)現(xiàn)value實(shí)際上的類型是CacheItem或者CacheEntity,它們都沒有無參的構(gòu)造函數(shù),所以無法反序列化。因此,這里我采用了字節(jié)轉(zhuǎn)換的方式。
從代碼中,可以看到document包含Key,Value,Type,Date(非必須的)的字段,其中Type在獲取緩存數(shù)據(jù)(Get)的時(shí)候非常有用。
對于獲取數(shù)據(jù):
獲取緩存數(shù)據(jù)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?反序列化字節(jié)數(shù)組
????????????????????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);是從表中根據(jù)指定的Document查詢數(shù)據(jù)。并且對于字節(jié)數(shù)據(jù)Value字段,必須進(jìn)行字節(jié)反序列化。
在Spring.NET對于NH的配置節(jié)點(diǎn)中可以這樣子寫:
代碼 <!--?MongoDB緩存機(jī)制?-->?<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配置的數(shù)據(jù)庫名稱;mongodb.connectionString是MongoDB服務(wù)的連接字符串;mongodb.pattern是為了作為表名稱的匹配正則表達(dá)式,可以看下這段代碼:
代碼 ///?<summary>?///?生成表格名稱?
///?</summary>?
///?<param?name="key"></param>?
private?void?GenerateTableName(object?key)?
{?
????if?(key?is?CacheKey)?
????{?
????????CacheKey?cacheKey?=?(CacheKey)key;?
????????//?判斷是否匹配正則表達(dá)式?
????????if?(Regex.IsMatch(cacheKey.EntityOrRoleName,?_pattern))?
????????{?
????????????_tableName?=?cacheKey.EntityOrRoleName.Replace(".",?"_");?
????????}?
????}?
}
它是通過CacheKey的EntityOrRoleName屬性,進(jìn)行篩選,比如:這里的EntityOrRoleName為”“TestWebServer.Model.TblEnterprise”的字符串(這是一個(gè)NH自動生成的實(shí)體類),我給它的正則表達(dá)式為“^TestWebServer\.Model\..+?”,那么它匹配了,我就取它的這個(gè)字符串為表名稱,最后的表名為:“TestWebServer_Model_TblEnterprise”。這樣我緩存每一個(gè)實(shí)體,都能夠自動創(chuàng)建相應(yīng)的一個(gè)Mongo表。
?
5. 看下運(yùn)行的結(jié)果:
測試代碼如下:
[Test]?public?void?EnterpriseDaoTest6()?
{?
????IEnterpriseDao?dao?=?(IEnterpriseDao)applicationContext.GetObject("EnterpriseDao");?
????ITblEnterprise?enterprise?=?dao.GetInfo(1);
????…
}
第一次執(zhí)行:
?
第一次的時(shí)候,執(zhí)行了數(shù)據(jù)庫的SELECT的SQL語句。
我查看本地目錄以及用MongoVUE客戶端工具查看了下Mongo數(shù)據(jù)庫:
緩存數(shù)據(jù)已經(jīng)存在目錄(數(shù)據(jù)庫)中。
第二次執(zhí)行:
發(fā)現(xiàn)這里沒有執(zhí)行SQL。
說明MongoDB緩存成功。
?
6. 通過對對于NHibernate二級緩存機(jī)制的理解,我們完全可以擴(kuò)展屬于我們自己的緩存組件。不僅僅是作為MongoDB為載體的緩存實(shí)現(xiàn)。
因此,在下一篇文章中,我將重點(diǎn)介紹關(guān)于NHibernate二級緩存機(jī)制的原理,并且繼續(xù)深入探討MongoDB緩存組件的相關(guān)原理。
?
NHibernate.Caches.MongoDBCache.dll項(xiàng)目源代碼下載:NHibernate.Caches.MongoDBCache.rar
總結(jié)
以上是生活随笔為你收集整理的设计一套基于NHibernate二级缓存的MongoDB组件(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: asp.net WebForm页面间传值
- 下一篇: 事业