戲說當年
大叔原創的分布式數據集緩存在之前的企業級框架里介紹過,大家可以關注《我心中的核心組件(可插拔的AOP)~第二回 緩存攔截器》,而今天主要對Lind.DDD.Caching進行更全面的解決,設計思想和主要核心內容進行講解。其實在很多緩存架構在業界有很多,向.net運行時里也有Cache,也可以實現簡單的數據緩存的功能,向前幾年頁面的靜態化比較流行,就出現了很多Http的“攔截器“,對當前HTTP響應的內容進行完整的頁面緩存,緩存的文件大多數存儲到磁盤里,訪問的時間直接將磁盤上的HTML文件進行輸出,不用asp.net進行解析,也省去了鏈數據庫的操作,所以在性能上有所提升,弊端就是和當前的頁面(HTML內容)耦合度太大,所以,現在用這種原始的緩存方式的項目越來越少。
大叔的數據集緩存
比較頁面緩存,數據集緩存就感覺優異了不少,它緩存的是數據,而不是頁面,即它省去了鏈接數據庫的時間,而直接用緩存,文件,redis等中間件上返回內容,當前你的中間件為了提升性能,可以采用集群機制,這在一些NoSql上實現非常容易,或者說Nosql就是為了緩存而產生的,呵呵!
緩存特性
這個CachingAttribute 特性被使用者添加到指定的方法上,有get,put,remove等枚舉類型,分別為讀緩存,寫緩存和刪除緩存。
/// <summary> /// 表示由此特性所描述的方法,能夠獲得來自Microsoft.Practices.EnterpriseLibrary.Caching基礎結構層所提供的緩存功能。 /// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=false)] public class CachingAttribute : Attribute { #region Ctor /// <summary> /// 初始化一個新的<c>CachingAttribute</c>類型。 /// </summary> /// <param name="method">緩存方式。</param> public CachingAttribute(CachingMethod method) { this.Method = method; } /// <summary> /// 初始化一個新的<c>CachingAttribute</c>類型。 /// </summary> /// <param name="method">緩存方式。</param> /// <param name="correspondingMethodNames">與當前緩存方式相關的方法名稱。注:此參數僅在緩存方式為Remove時起作用。</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> /// 獲取或設置一個<see cref="Boolean"/>值,該值表示當緩存方式為Put時,是否強制將值寫入緩存中。 /// </summary> public bool Force { get; set; } /// <summary> /// 獲取或設置與當前緩存方式相關的方法名稱。注:此參數僅在緩存方式為Remove時起作用。 /// </summary> public string[] CorrespondingMethodNames { get; set; } #endregion }
緩存攔截器
攔截器起源于面向切面的編程aop里,它也是aop設計的精髓,即將指定方法攔截,然后注入新的代碼邏輯,在不修改原有代碼的情況下,完成這個功能,在攔截器里,我們為不同的項目添加了不同的名稱,這是為了避免在多項目情況下,緩存鍵名重復的問題,因為我們的緩存內容都是存儲在同一個中間件上的。
/// <summary> /// 表示用于方法緩存功能的攔截行為。 /// </summary> public class CachingBehavior : IInterceptionBehavior { /// <summary> /// 緩存項目名稱,每個項目有自己的名稱 /// 避免緩存鍵名重復 /// </summary> static readonly string cacheProjectName = System.Configuration.ConfigurationManager.AppSettings["CacheProjectName"] ?? "DataSetCache"; #region Private Methods /// <summary> /// 根據指定的<see cref="CachingAttribute"/>以及<see cref="IMethodInvocation"/>實例, /// 獲取與某一特定參數值相關的鍵名。 /// </summary> /// <param name="cachingAttribute"><see cref="CachingAttribute"/>實例。</param> /// <param name="input"><see cref="IMethodInvocation"/>實例。</param> /// <returns>與某一特定參數值相關的鍵名。</returns> private string GetValueKey(CachingAttribute cachingAttribute, IMethodInvocation input) { switch (cachingAttribute.Method) { // 如果是Remove,則不存在特定值鍵名,所有的以該方法名稱相關的緩存都需要清除 case CachingMethod.Remove: return null; case CachingMethod.Get:// 如果是Get或者Put,則需要產生一個針對特定參數值的鍵名 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表達式異常,可能是沒有字段,如這種格式i=>true,會產生NullReferenceException異常. } sb.Append(result.ToString()); } else if (input.Arguments[i].GetType() != typeof(string)//類和結構體處理 && input.Arguments[i].GetType().BaseType.IsClass) { var obj = input.Arguments[i]; Type t = obj.GetType(); string result = ""; #region 提取類中的字段和屬性 foreach (var member in t.GetProperties())//公開屬性 { 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//簡單值類型處理 { sb.Append(input.Arguments[i].ToString()); } if (i != input.Arguments.Count - 1) sb.Append("_"); } return sb.ToString(); } else return "NULL"; default: throw new InvalidOperationException("無效的緩存方式。"); } } #endregion #region IInterceptionBehavior Members /// <summary> /// 獲取當前行為需要攔截的對象類型接口。 /// </summary> /// <returns>所有需要攔截的對象類型接口。</returns> public IEnumerable<Type> GetRequiredInterfaces() { return Type.EmptyTypes; } /// <summary> /// 通過實現此方法來攔截調用并執行所需的攔截行為。 /// </summary> /// <param name="input">調用攔截目標時的輸入信息。</param> /// <param name="getNext">通過行為鏈來獲取下一個攔截行為的委托。</param> /// <returns>從攔截目標獲得的返回信息。</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() + "_"; } } //鍵名,在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> /// 獲取一個<see cref="Boolean"/>值,該值表示當前攔截行為被調用時,是否真的需要執行 /// 某些操作。 /// </summary> public bool WillExecute { get { return true; } } #endregion }
緩存實現者
目前大叔的框架中,數據集緩存有redis和內容兩種實現方式,在多web服務器的情況下,只能采用redis這種中間存儲服務器。
緩存生產者
緩存生產者與日志,消息等生產者類似,由于全局使用一個實例即可,所以在設計時采用了單例模式,工廠模式,策略模式等,目前在工廠里只有EntLib內存緩存和redis分布式緩存兩種,詳細請見代碼。
/// <summary> /// 緩存持久化工廠類 /// 可以由多種持久化的策略 /// 策略模式和工廠模式的體現 /// </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> /// 對外不能創建類的實例 /// </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("緩存持久化方法不正確,目前只支持EntLib和Redis"); } } #endregion #region Public Properties /// <summary> /// 獲取<c>CacheManager</c>類型的單件(Singleton)實例。 /// </summary> public static CacheManager Instance { get { return _instance; } } #endregion #region ICacheProvider Members /// <summary> /// 向緩存中添加一個對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的對象。</param> public void Add(string key, string valKey, object value) { _cacheProvider.Add(key, valKey, value); } /// <summary> /// 向緩存中更新一個對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的對象。</param> public void Put(string key, string valKey, object value) { _cacheProvider.Put(key, valKey, value); } /// <summary> /// 從緩存中讀取對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <returns>被緩存的對象。</returns> public object Get(string key, string valKey) { return _cacheProvider.Get(key, valKey); } /// <summary> /// 從緩存中移除對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> public void Remove(string key) { _cacheProvider.Remove(key); } /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值的緩存是否存在。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <returns>如果緩存存在,則返回true,否則返回false。</returns> public bool Exists(string key) { return _cacheProvider.Exists(key); } /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值和緩存值鍵的緩存是否存在。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <param name="valKey">緩存值鍵。</param> /// <returns>如果緩存存在,則返回true,否則返回false。</returns> public bool Exists(string key, string valKey) { return _cacheProvider.Exists(key, valKey); } #endregion }
最后,我們的緩存使用需要在接口的方法或者虛方法上進行聲明,因為我們的攔截使用了Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension攔截器組件,實現原因是生成一個代理類,并重寫指定的被攔截的方法,所以要求你的方法是接口方法或者虛方法。
感謝各位的耐心閱讀,請繼續關注Lind.DDD大叔框架設計437541737
文章列表