在.NET項目中使用PostSharp,使用CacheManager實現多種緩存框架的處理

在前面幾篇隨筆中,介紹了PostSharp的使用,以及整合MemoryCache,《在.NET項目中使用PostSharp,實現AOP面向切面編程處理》、《在.NET項目中使用PostSharp,使用MemoryCache實現緩存的處理》參數了對PostSharp的使用,並介紹了MemoryCache的緩存使用,可是緩存框架的世界裏面,有不少成熟的緩存框架,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等,這時候咱們若是有一個大內總管或者一個吸星大法的武功,把它們融合起來,那麼就真的是很是完美的一件事情,這個就是咱們CacheManager緩存框架了,這樣的靈活性緩存框架並結合了PostSharp橫切面對常規代碼的簡化功能,簡直就是好鞍配好馬、寶劍贈英雄,整合起來處理緩存真的是如虎添翼。html

一、CacheManager緩存框架的回顧

關於這個緩存框架,我在隨筆《.NET緩存框架CacheManager在混合式開發框架中的應用(1)-CacheManager的介紹和使用》中進行了介紹,讀者能夠從中瞭解一下CacheManager緩存框架到底是一個什麼樣的東西。git

CacheManager是一個以C#語言開發的開源.Net緩存框架抽象層。它不是具體的緩存實現,但它支持多種緩存提供者(如Redis、Memcached等)並提供不少高級特性。
CacheManager 主要的目的使開發者更容易處理各類複雜的緩存場景,使用CacheManager能夠實現多層的緩存,讓進程內緩存在分佈式緩存以前,且僅需幾行代碼來處理。
CacheManager 不只僅是一個接口去統一不一樣緩存提供者的編程模型,它使咱們在一個項目裏面改變緩存策略變得很是容易,同時也提供更多的特性:如緩存同步、併發更新、序列號、事件處理、性能計算等等,開發人員能夠在須要的時候選擇這些特性。github

CacheManager緩存框架支持Winform和Web等應用開發,以及支持多種流行的緩存實現,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等。redis

縱觀整個緩存框架,它的特定很明顯,在支持多種緩存實現外,自己主要是之內存緩存(進程內)爲主,其餘分佈式緩存爲輔的多層緩存架構方式,以達到快速命中和處理的機制,它們內部有相關的消息處理,使得即便是分佈式緩存,也可以及時實現併發同步的緩存處理。數據庫

CacheManager緩存框架在配置方面,支持代碼方式的配置、XML配置,以及JSON格式的配置處理,很是方便。編程

CacheManager緩存框架默認對緩存數據的序列化是採用二進制方式,同時也支持多種自定義序列化的方式,如基於JOSN.NET的JSON序列化或者自定義序列化方式。緩存

CacheManager緩存框架能夠對緩存記錄的增長、刪除、更新等相關事件進行記錄。架構

CacheManager緩存框架的緩存數據是強類型的,能夠支持各類常規類型的處理,如Int、String、List類型等各類基礎類型,以及可序列號的各類對象及列表對象。併發

CacheManager緩存框架支持多層的緩存實現,內部良好的機制能夠高效、及時的同步好各層緩存的數據。框架

CacheManager緩存框架支持對各類操做的日誌記錄。

CacheManager緩存框架在分佈式緩存實現中支持對更新的鎖定和事務處理,讓緩存保持更好的同步處理,內部機制實現版本衝突處理。

CacheManager緩存框架支持兩種緩存過時的處理,如絕對時間的過時處理,以及固定時段的過時處理,是咱們處理緩存過時更加方便。

....

不少特性基本上覆蓋了緩存的常規特性,並且提供的接口基本上也是咱們所常常用的Add、Put、Update、Remove等接口,使用起來也很是方便。

CacheManager的GitHub源碼地址爲:https://github.com/MichaCo/CacheManager,若是須要具體的Demo及說明,能夠訪問其官網:http://cachemanager.net/

 

通常來講,對於單機版本的應用場景,基本上是無需引入這種緩存框架的,由於客戶端的併發量不多,並且數據請求也是寥寥可數的,性能方便不會有任何問題。

若是對於分佈式的應用系統,如我在不少隨筆中介紹到個人《混合式開發框架》、《Web開發框架》,因爲數據請求是併發量隨着用戶增加而增加的,特別對於一些互聯網的應用系統,極端狀況下某個時間點一下可能就會達到了整個應用併發的峯值。那麼這種分佈式的系統架構,引入數據緩存來下降IO的併發數,把耗時請求轉換爲內存的高速請求,能夠極大程度的下降系統宕機的風險。

咱們以基於常規的Web API層來構建應用框架爲例,整個數據緩存層,應該是在Web API層之下、業務實現層之上的一個層,以下所示。

 

二、整合PostSharp和CacheManager實現多種緩存框架的處理

因爲MemoryCache是在單個機器上進行緩存的處理,並且沒法進行序列號,電腦宕機後就會所有丟掉緩存內容,因爲這個缺點,咱們對《在.NET項目中使用PostSharp,使用MemoryCache實現緩存的處理》基礎上進行進一步的調整,整合CacheManager進行使,從而能夠利用緩存彈性化處理以及可序列號的特色。

咱們在正常狀況下,仍是須要使用Redis這個強大的分佈式緩存的,關於Redis的安裝和使用,請參考個人隨筆《基於C#的MongoDB數據庫開發應用(4)--Redis的安裝及使用》。

咱們首先定義一個CacheAttribute的Aspect類,用來對緩存的切面處理。

    /// <summary>
    /// 方法實現緩存的標識
    /// </summary>
    [Serializable]
    public class CacheAttribute : MethodInterceptionAspect
    {
        /// <summary>
        /// 緩存的失效時間設置,默認採用30分鐘
        /// </summary>
        public int ExpirationPeriod = 30;

        /// <summary>
        /// PostSharp的調用處理,實現數據的緩存處理
        /// </summary>
        public override void OnInvoke(MethodInterceptionArgs args)
        {
            //默認30分鐘失效,若是設置過時時間,那麼採用設置值
            TimeSpan timeSpan = new TimeSpan(0, 0, ExpirationPeriod, 0);

            var cache = MethodResultCache.GetCache(args.Method, timeSpan);
            var arguments = args.Arguments.ToList();//args.Arguments.Union(new[] {WindowsIdentity.GetCurrent().Name}).ToList();
            var result = cache.GetCachedResult(arguments);
            if (result != null)
            {
                args.ReturnValue = result;
                return;
            }
            else
            {
                base.OnInvoke(args);
                //調用後更新緩存
                cache.CacheCallResult(args.ReturnValue, arguments);
            }
        }
    }

而後就是進一步處理完善類 MethodResultCache來對緩存數據進行處理了。該類負責構造一個CacheManager管理類來對緩存進行處理,以下代碼所示。

初始化緩存管理器的代碼以下所示,這裏利用了MemoryCache做爲快速的內存緩存(主緩存),以及Redis做爲序列化存儲的緩存容器(從緩存),它們有內在機制進行同步處理。

        /// <summary>
        /// 初始化緩存管理器
        /// </summary>
        private void InitCacheManager()
        {
            _cache = CacheFactory.Build("getStartedCache", settings =>
            {
                settings
                .WithSystemRuntimeCacheHandle("handleName")
                .And
                .WithRedisConfiguration("redis", config =>
                {
                    config.WithAllowAdmin()
                        .WithDatabase(0)
                        .WithEndpoint("localhost", 6379);
                })
                .WithMaxRetries(100)
                .WithRetryTimeout(50)
                .WithRedisBackplane("redis")
                .WithRedisCacheHandle("redis", true)
                ;
            });
        }

對緩存結果進行處理的函數以下所示。

        /// <summary>
        /// 緩存結果內容
        /// </summary>
        /// <param name="result">待加入緩存的結果</param>
        /// <param name="arguments">方法的參數集合</param>
        public void CacheCallResult(object result, IEnumerable<object> arguments)
        {
            var key = GetCacheKey(arguments);
            _cache.Remove(key);

            var item = new CacheItem<object>(key, result, ExpirationMode.Sliding, _expirationPeriod);
            _cache.Add(item);
        }

首先就是獲取方法參數的鍵,而後移除對應的緩存,加入新的緩存,並設定緩存的失效時間段便可。

清空緩存的時候,直接調用管理類的Clear方法便可達到目的。

        /// <summary>
        /// 清空方法的緩存
        /// </summary>
        public void ClearCachedResults()
        {
            _cache.Clear();
        } 

這樣,咱們處理好後,在一個業務調用類裏面進行設置緩存標誌便可,以下代碼所示。

        /// <summary>
        /// 獲取用戶所有簡單對象信息,並放到緩存裏面
        /// </summary>
        /// <returns></returns>
        [Cache(ExpirationPeriod = 1)]
        public static List<SimpleUserInfo> GetSimpleUsers(int userid)
        {
            Thread.Sleep(500);
            //return CallerFactory<IUserService>.Instance.GetSimpleUsers(); 

            //模擬從數據庫獲取數據
            List<SimpleUserInfo> list = new List<SimpleUserInfo>();
            for (int i = 0; i < 10; i++)
            {
                var info = new SimpleUserInfo();
                info.ID = i;
                info.Name = string.Concat("Name:", i);
                info.FullName = string.Concat("姓名:", i);
                list.Add(info);
            }
            return list;
        }

爲了測試緩存的處理,以及對Redis的支持狀況,我編寫了一個簡單的案例,功能以下所示。

測試代碼以下所示。

        //測試緩存
        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine(" 測試緩存: ");

            //測試反覆調用獲取數值的耗時
            DateTime start = DateTime.Now;
            var list = CacheService.GetSimpleUsers(1);
            int end = (int)DateTime.Now.Subtract(start).TotalMilliseconds;

            Console.WriteLine(" first: " + end);
            Console.WriteLine(" List: " + list.Count);

            //Second test
            //檢查不一樣的方法參數,對緩存值的影響
            start = DateTime.Now;
            list = CacheService.GetSimpleUsers(2);
            end = (int)DateTime.Now.Subtract(start).TotalMilliseconds;
            Console.WriteLine(" Second: " + end);
            Console.WriteLine(" List2: " + list.Count);
        }
           
        //更新緩存
        private void button2_Click(object sender, EventArgs e)
        {
            Console.WriteLine(" 更新緩存: ");

            //首先獲取對應鍵的緩存值
            //而後對緩存進行修改
            //最後從新加入緩存
            var key = "CacheManagerAndPostSharp.CacheService.GetSimpleUsers";
            var item = MethodResultCache.GetCache(key);
            var argument = new List<object>(){1};
            var result = item.GetCachedResult(argument);
            Console.WriteLine("OldResult:" + result.ToJson());

            List<SimpleUserInfo> newList = result as List<SimpleUserInfo>;
            if(newList != null)
            {
                newList.Add(new SimpleUserInfo() { ID = new Random().Next(), Name = RandomChinese.GetRandomChars(2) });
            }
            item.CacheCallResult(newList, argument);
        }

        //清空緩存
        private void button3_Click(object sender, EventArgs e)
        {
            Console.WriteLine(" 清空緩存: ");

            //首先獲取對應鍵的緩存值
            var key = "CacheManagerAndPostSharp.CacheService.GetSimpleUsers";
            var item = MethodResultCache.GetCache(key);
            var argument = new List<object>(){1};

            //而後清空方法的全部緩存
            item.ClearCachedResults();

            //最後從新檢驗緩存值爲空
            var result = item.GetCachedResult(argument);
            Console.WriteLine("Result:" + result !=null ? result.ToJson() : "null");
        }

測試運行結果以下所示。

 測試緩存: 
 first: 870
 List: 10
 Second: 502
 List2: 10

 更新緩存: 
OldResult:[
{
"ID":0,"HandNo":null,"Name":"Name:0","Password":null,"FullName":"姓名:0","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":1,"HandNo":null,"Name":"Name:1","Password":null,"FullName":"姓名:1","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":2,"HandNo":null,"Name":"Name:2","Password":null,"FullName":"姓名:2","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":3,"HandNo":null,"Name":"Name:3","Password":null,"FullName":"姓名:3","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":4,"HandNo":null,"Name":"Name:4","Password":null,"FullName":"姓名:4","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":5,"HandNo":null,"Name":"Name:5","Password":null,"FullName":"姓名:5","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":6,"HandNo":null,"Name":"Name:6","Password":null,"FullName":"姓名:6","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":7,"HandNo":null,"Name":"Name:7","Password":null,"FullName":"姓名:7","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":8,"HandNo":null,"Name":"Name:8","Password":null,"FullName":"姓名:8","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null},
{"ID":9,"HandNo":null,"Name":"Name:9","Password":null,"FullName":"姓名:9","MobilePhone":null,"Email":null,"CurrentLoginUserId":null,"Data1":null,"Data2":null,"Data3":null}] 測試緩存: first: 0 List: 11 Second: 0 List2: 10 清空緩存: null

同時咱們看到在Redis裏面,有相關的記錄以下所示。

結合PostSharp和CacheManager,使得咱們在使用緩存方面更具備彈性化,能夠根據狀況經過配置實現使用不一樣的緩存處理,可是在代碼中使用緩存就是隻須要聲明一下便可,很是方便簡潔了。

相關文章
相關標籤/搜索