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