在前面幾篇隨筆中,介紹了PostSharp的使用,以及整合MemoryCache,《在.NET項目中使用PostSharp,實現AOP面向切面編程處理》、《在.NET項目中使用PostSharp,使用MemoryCache實現緩存的處理》參數了對PostSharp的使用,並介紹了MemoryCache的緩存使用,可是緩存框架的世界裏面,有不少成熟的緩存框架,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等,這時候咱們若是有一個大內總管或者一個吸星大法的武功,把它們融合起來,那麼就真的是很是完美的一件事情,這個就是咱們CacheManager緩存框架了,這樣的靈活性緩存框架並結合了PostSharp橫切面對常規代碼的簡化功能,簡直就是好鞍配好馬、寶劍贈英雄,整合起來處理緩存真的是如虎添翼。html
關於這個緩存框架,我在隨筆《.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層之下、業務實現層之上的一個層,以下所示。
因爲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,使得咱們在使用緩存方面更具備彈性化,能夠根據狀況經過配置實現使用不一樣的緩存處理,可是在代碼中使用緩存就是隻須要聲明一下便可,很是方便簡潔了。