Lind.DDD.Caching分(fen)布式數據集(ji)緩存介紹
戲說當年
大叔原創的分布式數據集緩存在之前的企業級框架里介紹過,大家可以關注《我心中的核心組件(可插拔的AOP)~第二回 緩存攔截器》,而今(jin)天(tian)主要對Lind.DDD.Caching進(jin)行(xing)(xing)更全面的(de)(de)(de)(de)(de)解(jie)決,設計思想(xiang)和(he)主要核(he)心(xin)內容進(jin)行(xing)(xing)講解(jie)。其實(shi)在(zai)很(hen)多緩(huan)存(cun)(cun)架構在(zai)業(ye)界有(you)很(hen)多,向(xiang).net運行(xing)(xing)時(shi)里也(ye)(ye)有(you)Cache,也(ye)(ye)可以實(shi)現(xian)簡單的(de)(de)(de)(de)(de)數據(ju)(ju)緩(huan)存(cun)(cun)的(de)(de)(de)(de)(de)功能,向(xiang)前幾年頁面的(de)(de)(de)(de)(de)靜態化比(bi)較流行(xing)(xing),就出(chu)現(xian)了(le)很(hen)多Http的(de)(de)(de)(de)(de)“攔截(jie)器“,對當前HTTP響應的(de)(de)(de)(de)(de)內容進(jin)行(xing)(xing)完整的(de)(de)(de)(de)(de)頁面緩(huan)存(cun)(cun),緩(huan)存(cun)(cun)的(de)(de)(de)(de)(de)文件大(da)多數存(cun)(cun)儲到(dao)磁(ci)盤里,訪問的(de)(de)(de)(de)(de)時(shi)間直接將磁(ci)盤上的(de)(de)(de)(de)(de)HTML文件進(jin)行(xing)(xing)輸出(chu),不(bu)用(yong)asp.net進(jin)行(xing)(xing)解(jie)析(xi),也(ye)(ye)省去了(le)鏈(lian)數據(ju)(ju)庫的(de)(de)(de)(de)(de)操(cao)作,所以在(zai)性能上有(you)所提升,弊端(duan)就是和(he)當前的(de)(de)(de)(de)(de)頁面(HTML內容)耦(ou)合(he)度太(tai)大(da),所以,現(xian)在(zai)用(yong)這種原始的(de)(de)(de)(de)(de)緩(huan)存(cun)(cun)方式(shi)的(de)(de)(de)(de)(de)項目越(yue)來(lai)越(yue)少。
大叔的數據集緩存
比較頁面緩存(cun),數據(ju)集緩存(cun)就(jiu)感(gan)覺優(you)異了(le)(le)不(bu)少,它(ta)緩存(cun)的(de)(de)是(shi)數據(ju),而(er)不(bu)是(shi)頁面,即它(ta)省去了(le)(le)鏈接數據(ju)庫(ku)的(de)(de)時間,而(er)直接用緩存(cun),文件,redis等中(zhong)間件上(shang)返回內容(rong),當前你的(de)(de)中(zhong)間件為(wei)了(le)(le)提(ti)升(sheng)性(xing)能,可以采(cai)用集群機制(zhi),這在一些NoSql上(shang)實現非(fei)常容(rong)易(yi),或(huo)者(zhe)說(shuo)Nosql就(jiu)是(shi)為(wei)了(le)(le)緩存(cun)而(er)產生的(de)(de),呵呵!
緩存特性
這個CachingAttribute 特(te)性(xing)被使用者添加(jia)到指定(ding)的方法(fa)上,有get,put,remove等枚(mei)舉(ju)類型,分(fen)別為讀緩存,寫緩存和(he)刪除緩存。
/// <summary> /// 表示由此特性所描(miao)述的方法,能(neng)夠獲得(de)來自(zi)Microsoft.Practices.EnterpriseLibrary.Caching基礎結構層所提供(gong)的緩存功(gong)能(neng)。 /// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=false)] public class CachingAttribute : Attribute { #region Ctor /// <summary> /// 初始化一個(ge)新的<c>CachingAttribute</c>類(lei)型。 /// </summary> /// <param name="method">緩(huan)存(cun)方式。</param> public CachingAttribute(CachingMethod method) { this.Method = method; } /// <summary> /// 初始(shi)化一(yi)個新(xin)的<c>CachingAttribute</c>類型(xing)。 /// </summary> /// <param name="method">緩存方(fang)式。</param> /// <param name="correspondingMethodNames">與(yu)當前(qian)緩(huan)存方(fang)式(shi)相關的方(fang)法名稱(cheng)。注:此參數僅(jin)在(zai)緩(huan)存方(fang)式(shi)為Remove時起作(zuo)用(yong)。</param> public CachingAttribute(CachingMethod method, params string[] correspondingMethodNames) : this(method) { this.CorrespondingMethodNames = correspondingMethodNames; } #endregion #region Public Properties /// <summary> /// 獲取或設置緩存方式。 /// </summary> public CachingMethod Method { get; set; } /// <summary> /// 獲取或設置一個(ge)<see cref="Boolean"/>值,該值表示當(dang)緩存方式為Put時,是否(fou)強制將值寫(xie)入緩存中。 /// </summary> public bool Force { get; set; } /// <summary> /// 獲取(qu)或設置與當前(qian)緩(huan)存方(fang)式(shi)相關的方(fang)法(fa)名稱。注:此(ci)參數(shu)僅在緩(huan)存方(fang)式(shi)為Remove時起作用(yong)。 /// </summary> public string[] CorrespondingMethodNames { get; set; } #endregion }
緩存攔截器
攔(lan)截器起源于面向切面的(de)(de)(de)編程aop里(li),它也是aop設計的(de)(de)(de)精髓,即將指(zhi)定(ding)方法攔(lan)截,然后注(zhu)入新的(de)(de)(de)代(dai)碼(ma)邏輯,在(zai)(zai)不(bu)修改原有代(dai)碼(ma)的(de)(de)(de)情況下,完成(cheng)這個功能,在(zai)(zai)攔(lan)截器里(li),我(wo)們為不(bu)同(tong)的(de)(de)(de)項目(mu)添加了(le)不(bu)同(tong)的(de)(de)(de)名(ming)稱,這是為了(le)避免在(zai)(zai)多項目(mu)情況下,緩(huan)(huan)存鍵名(ming)重復的(de)(de)(de)問題(ti),因為我(wo)們的(de)(de)(de)緩(huan)(huan)存內(nei)容(rong)都是存儲在(zai)(zai)同(tong)一個中(zhong)間件(jian)上(shang)的(de)(de)(de)。
/// <summary> /// 表示用于方法緩存(cun)功(gong)能的攔截行為。 /// </summary> public class CachingBehavior : IInterceptionBehavior { /// <summary> /// 緩存項(xiang)目(mu)名稱,每(mei)個項(xiang)目(mu)有自己(ji)的名稱 /// 避免緩存鍵名(ming)重復 /// </summary> static readonly string cacheProjectName = System.Configuration.ConfigurationManager.AppSettings["CacheProjectName"] ?? "DataSetCache"; #region Private Methods /// <summary> /// 根(gen)據指定的(de)<see cref="CachingAttribute"/>以及<see cref="IMethodInvocation"/>實例, /// 獲取與某一(yi)特定參(can)數(shu)值相關(guan)的鍵名。 /// </summary> /// <param name="cachingAttribute"><see cref="CachingAttribute"/>實例。</param> /// <param name="input"><see cref="IMethodInvocation"/>實例。</param> /// <returns>與某(mou)一(yi)特(te)定參數值相關(guan)的鍵名。</returns> private string GetValueKey(CachingAttribute cachingAttribute, IMethodInvocation input) { switch (cachingAttribute.Method) { // 如果是Remove,則不(bu)存在特定(ding)值鍵名,所有的以該(gai)方法名稱相關的緩存都需要清除 case CachingMethod.Remove: return null; case CachingMethod.Get:// 如果是(shi)Get或者Put,則需要(yao)產(chan)生一(yi)個針對特定參數(shu)值(zhi)的鍵名(ming) case CachingMethod.Put: if (input.Arguments != null && input.Arguments.Count > 0) { var sb = new StringBuilder(); for (int i = 0; i < input.Arguments.Count; i++) { if (input.Arguments[i] == null) break; if (input.Arguments[i].GetType().BaseType == typeof(LambdaExpression))//lambda處理 { string result = ""; try { var exp = input.Arguments[i] as LambdaExpression; var arr = ((System.Runtime.CompilerServices.Closure)(((System.Delegate)(Expression.Lambda(exp).Compile().DynamicInvoke())).Target)).Constants; Type t = arr[0].GetType(); foreach (var member in t.GetFields()) { result += "_" + member.Name + "_" + t.GetField(member.Name).GetValue(arr[0]); } result = result.Remove(0, 1); } catch (NullReferenceException) { //lambda表達式異(yi)常(chang),可(ke)能是(shi)沒有字段(duan),如這種格式i=>true,會(hui)產生NullReferenceException異(yi)常(chang). } sb.Append(result.ToString()); } else if (input.Arguments[i].GetType() != typeof(string)//類和結(jie)構體處理 && input.Arguments[i].GetType().BaseType.IsClass) { var obj = input.Arguments[i]; Type t = obj.GetType(); string result = ""; #region 提取類中的字段和屬性 foreach (var member in t.GetProperties())//公開屬性(xing) { result += member.Name + "_" + t.GetProperty(member.Name).GetValue(obj) + "_"; } foreach (var member in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))//私有和公用字段 { result += member.Name + "_" + t.GetField(member.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetValue(obj) + "_"; } #endregion result = result.Remove(result.Length - 1); sb.Append(result.ToString()); } else//簡單(dan)值類型(xing)處理 { sb.Append(input.Arguments[i].ToString()); } if (i != input.Arguments.Count - 1) sb.Append("_"); } return sb.ToString(); } else return "NULL"; default: throw new InvalidOperationException("無(wu)效(xiao)的緩存方(fang)式。"); } } #endregion #region IInterceptionBehavior Members /// <summary> /// 獲取當(dang)前行為需要攔截(jie)的對象類型接口。 /// </summary> /// <returns>所有需要攔(lan)截(jie)的對象(xiang)類型接口。</returns> public IEnumerable<Type> GetRequiredInterfaces() { return Type.EmptyTypes; } /// <summary> /// 通過實現(xian)此(ci)方法來攔(lan)截(jie)調用并執(zhi)行所(suo)需的(de)攔(lan)截(jie)行為(wei)。 /// </summary> /// <param name="input">調用攔截(jie)目(mu)標時的輸入信息。</param> /// <param name="getNext">通(tong)過行為鏈來獲取下一(yi)個攔(lan)截行為的委(wei)托。</param> /// <returns>從攔(lan)截目標獲得的返回信息。</returns> public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) { var method = input.MethodBase; //鍵值前綴 string prefix = cacheProjectName + "_"; var baseInterfaces = input.Target.GetType().GetInterfaces(); if (baseInterfaces != null && baseInterfaces.Any()) { foreach (var item in baseInterfaces) { prefix += item.ToString() + "_"; } } //鍵名(ming),在put和get時使用 var key = prefix + method.Name; if (method.IsDefined(typeof(CachingAttribute), false)) { var cachingAttribute = (CachingAttribute)method.GetCustomAttributes(typeof(CachingAttribute), false)[0]; var valKey = GetValueKey(cachingAttribute, input); switch (cachingAttribute.Method) { case CachingMethod.Get: try { if (CacheManager.Instance.Exists(key, valKey)) { var obj = CacheManager.Instance.Get(key, valKey); var arguments = new object[input.Arguments.Count]; input.Arguments.CopyTo(arguments, 0); return new VirtualMethodReturn(input, obj, arguments); } else { var methodReturn = getNext().Invoke(input, getNext); CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue); return methodReturn; } } catch (Exception ex) { return new VirtualMethodReturn(input, ex); } case CachingMethod.Put: try { var methodReturn = getNext().Invoke(input, getNext); if (CacheManager.Instance.Exists(key)) { if (cachingAttribute.Force) { CacheManager.Instance.Remove(key); CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue); } else CacheManager.Instance.Put(key, valKey, methodReturn.ReturnValue); } else CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue); return methodReturn; } catch (Exception ex) { return new VirtualMethodReturn(input, ex); } case CachingMethod.Remove: try { var removeKeys = cachingAttribute.CorrespondingMethodNames; foreach (var removeKey in removeKeys) { string delKey = prefix + removeKey; if (CacheManager.Instance.Exists(delKey)) CacheManager.Instance.Remove(delKey); } var methodReturn = getNext().Invoke(input, getNext); return methodReturn; } catch (Exception ex) { return new VirtualMethodReturn(input, ex); } default: break; } } return getNext().Invoke(input, getNext); } /// <summary> /// 獲取一個(ge)<see cref="Boolean"/>值,該值表示(shi)當前攔(lan)截(jie)行(xing)(xing)為被(bei)調用時,是否(fou)真的(de)需(xu)要執(zhi)行(xing)(xing) /// 某些(xie)操作(zuo)。 /// </summary> public bool WillExecute { get { return true; } } #endregion }
緩存實現者
目前大(da)叔(shu)的(de)框架(jia)中,數據集(ji)緩存(cun)有redis和(he)內容(rong)兩種(zhong)實現(xian)方式,在多web服(fu)務器的(de)情況下,只能采用(yong)redis這種(zhong)中間存(cun)儲服(fu)務器。
緩存生產者
緩存生產(chan)者與日志,消息(xi)等生產(chan)者類似,由于全局使用一個實例即可,所(suo)以在設計(ji)時采用了單例模(mo)式,工廠模(mo)式,策略模(mo)式等,目(mu)前(qian)在工廠里只(zhi)有(you)EntLib內(nei)存緩存和redis分布式緩存兩種,詳(xiang)細請見代(dai)碼(ma)。
/// <summary> /// 緩存持久化工廠類 /// 可以由多種持久化的策(ce)略 /// 策略模(mo)(mo)式和工(gong)廠模(mo)(mo)式的體現 /// </summary> public sealed class CacheManager : ICacheProvider { #region Private Fields private readonly ICacheProvider _cacheProvider; private static readonly CacheManager _instance; #endregion #region Ctor static CacheManager() { _instance = new CacheManager(); } /// <summary> /// 對(dui)外不(bu)能創建類的實例 /// </summary> private CacheManager() { string strategyName = ConfigConstants.ConfigManager.Config.AoP_CacheStrategy ?? "EntLib"; switch (strategyName) { case "EntLib": _cacheProvider = new EntLibCacheProvider(); break; case "Redis": _cacheProvider = new RedisCacheProvider(); break; default: throw new ArgumentException("緩存持久化方法不正確,目前(qian)只支持EntLib和Redis"); } } #endregion #region Public Properties /// <summary> /// 獲取(qu)<c>CacheManager</c>類型(xing)的單件(Singleton)實例。 /// </summary> public static CacheManager Instance { get { return _instance; } } #endregion #region ICacheProvider Members /// <summary> /// 向(xiang)緩存(cun)中添(tian)加一個對象(xiang)。 /// </summary> /// <param name="key">緩存(cun)的(de)鍵值(zhi),該值(zhi)通常是使用緩存(cun)機制(zhi)的(de)方法的(de)名稱。</param> /// <param name="valKey">緩存(cun)值的鍵(jian)值,該值通(tong)常是由(you)使用緩存(cun)機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的(de)對象。</param> public void Add(string key, string valKey, object value) { _cacheProvider.Add(key, valKey, value); } /// <summary> /// 向(xiang)緩存中更(geng)新(xin)一個對(dui)象。 /// </summary> /// <param name="key">緩存的(de)(de)鍵值,該(gai)值通常是(shi)使用緩存機制的(de)(de)方(fang)法(fa)的(de)(de)名(ming)稱(cheng)。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由(you)使用緩存機制(zhi)的方法(fa)的參數值所(suo)產生。</param> /// <param name="value">需要緩(huan)存的對象。</param> public void Put(string key, string valKey, object value) { _cacheProvider.Put(key, valKey, value); } /// <summary> /// 從緩(huan)存中讀取對象。 /// </summary> /// <param name="key">緩(huan)存(cun)的(de)(de)鍵(jian)值,該值通常(chang)是使用緩(huan)存(cun)機制的(de)(de)方法的(de)(de)名稱。</param> /// <param name="valKey">緩存值(zhi)(zhi)的鍵值(zhi)(zhi),該值(zhi)(zhi)通常是由使用緩存機(ji)制的方(fang)法的參數(shu)值(zhi)(zhi)所產生。</param> /// <returns>被緩存的對象(xiang)。</returns> public object Get(string key, string valKey) { return _cacheProvider.Get(key, valKey); } /// <summary> /// 從緩(huan)存中移(yi)除對象(xiang)。 /// </summary> /// <param name="key">緩存的鍵(jian)值(zhi),該值(zhi)通常是使用(yong)緩存機制的方法的名稱。</param> public void Remove(string key) { _cacheProvider.Remove(key); } /// <summary> /// 獲取一(yi)個<see cref="Boolean"/>值,該值表示擁有(you)指(zhi)定鍵值的緩(huan)存是(shi)否(fou)存在(zai)。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <returns>如果緩存(cun)存(cun)在,則(ze)返回(hui)true,否則(ze)返回(hui)false。</returns> public bool Exists(string key) { return _cacheProvider.Exists(key); } /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值和緩(huan)存值鍵的緩(huan)存是否存在。 /// </summary> /// <param name="key">指(zhi)定的(de)鍵值。</param> /// <param name="valKey">緩存值(zhi)鍵。</param> /// <returns>如(ru)果(guo)緩存存在,則返回true,否則返回false。</returns> public bool Exists(string key, string valKey) { return _cacheProvider.Exists(key, valKey); } #endregion }
最后,我(wo)(wo)們(men)的(de)(de)緩存使(shi)用(yong)需要在接(jie)口(kou)的(de)(de)方法(fa)(fa)(fa)或(huo)者虛方法(fa)(fa)(fa)上進行聲明(ming),因為(wei)我(wo)(wo)們(men)的(de)(de)攔(lan)截使(shi)用(yong)了Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension攔(lan)截器組件,實現原因是(shi)生成一個(ge)代理類,并重寫指定的(de)(de)被攔(lan)截的(de)(de)方法(fa)(fa)(fa),所以要求你(ni)的(de)(de)方法(fa)(fa)(fa)是(shi)接(jie)口(kou)方法(fa)(fa)(fa)或(huo)者虛方法(fa)(fa)(fa)。
感謝各位的耐心閱讀,請繼續關注Lind.DDD大叔框架設計437541737