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


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

1、CacheManager緩存框架的回顧

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

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

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

縱觀整個緩存框架,它的特定很明顯,在支持多種緩存實現外,本身主要是以內存緩存(進程內)為主,其他分布式緩存為輔的多層緩存架構方式,以達到快速命中和處理的機制,它們內部有相關的消息處理,使得即使是分布式緩存,也能夠及時實現並發同步的緩存處理。

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層之下、業務實現層之上的一個層,如下所示。

 

2、整合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,使得我們在使用緩存方面更具有彈性化,可以根據情況通過配置實現使用不同的緩存處理,但是在代碼中使用緩存就是只需要聲明一下即可,非常方便簡潔了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM