Lind.DDD.Caching分散式資料集快取介紹
戲說當年
大叔原創的分散式資料集快取在之前的企業級框架裡介紹過,大家可以關注《我心中的核心元件(可插拔的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