1. 程式人生 > >Lind.DDD.Caching分布式數據集緩存介紹

Lind.DDD.Caching分布式數據集緩存介紹

toolbar 中間件 erp dsw vid 集群 break auc var

戲說當年

大叔原創的分布式數據集緩存在之前的企業級框架裏介紹過,大家可以關註《我心中的核心組件(可插拔的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大叔框架設計

Lind.DDD.Caching分布式數據集緩存介紹