.Net緩存管理框架CacheManager
Cache緩存在計算機領域是一個被普遍使用的概念。硬件中CPU有一級緩存,二級緩存, 瀏覽器中有緩存,軟件開發中也有分布式緩存memcache, redis。緩存無處不在的原因是它能夠極大地提高硬件和軟件的運行速度。在項目開發中,性能慢的地方常常是IO操作頻繁的地方,讀取數據庫是我們常見的消耗性能的地方。這個時候,如果將使用頻繁的數據緩存到能夠高速讀取的介質中,下次訪問時,不用再去請求數據庫,直接從緩存中獲取所需的數據,就能夠大大提高性能。這篇文章主要討論的是在.Net開發中,如何使用CacheManager框架方便的管理項目中的緩存。
一,CacheManager介紹以及優點
CacheManager是開源的.Net緩存管理框架。它不是具體的緩存實現,而是在緩存之上,方便開發人員配置和管理各種不同的緩存,為上層應用程序提供統一的緩存接口的中間層。
下面是CacheManager的一些優點:
- 讓開發人員的生活更容易處理和配資緩存,即使是非常復雜的緩存方案。
- CacheManager能夠管理多種緩存,包含 內存, appfabric, redis, couchbase, windows azure cache, memorycache等。
- 提供了額外的功能,如緩存同步、並發更新、事件、性能計數器等…
二,CacheManager開始之旅
CacheManager上手還是非常簡單的。下面使用內存緩存結合CacheManager的一個實例,能夠幫助我們快速的熟悉CacheManager如何使用。
首先在Visual Studio中創建一個Console Application.
使用Nuget為項目添加CacheManager包引用。CacheManager包含了很多的Package. 其中CacheManager.Core是必須的,其它的針對不同緩存平台上有不同的對應Package.
這個Demo中,我們使用內存作為緩存,所以只是需要CacheManager.Core和CacheManager.SystemRuntimeCaching
接着在Main函數中配置好我們的緩存:
1 using System; 2 using CacheManager.Core; 3 namespace ConsoleApplication 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 var cache = CacheFactory.Build("getStartedCache", settings => 10 { 11 settings.WithSystemRuntimeCacheHandle("handleName"); 12 }); 13 } 14 } 15 }
上面代碼中使用CacheFactory創建了一個名稱為getStartedCache的緩存實例,這個緩存實例使用的是SystemRunTime Cache, 內存緩存。一個緩存實例是可以配置多個Handle的,我們可以使用內存來作為存儲介質,也可以使用Redis分布式緩存作為存儲介質,並且可以同時在一個緩存實例中使用,后面我們再介紹多級緩存的配置和使用。
接下來,我們添加一些測試緩存的代碼
1 static void Main(string[] args) 2 { 3 4 var cache = CacheFactory.Build("getStartedCache", settings => 5 { 6 settings.WithSystemRuntimeCacheHandle("handleName"); 7 }); 8 9 cache.Add("keyA", "valueA"); 10 cache.Put("keyB", 23); 11 cache.Update("keyB", v => 42); 12 Console.WriteLine("KeyA is " + cache.Get("keyA")); // should be valueA 13 Console.WriteLine("KeyB is " + cache.Get("keyB")); // should be 42 14 cache.Remove("keyA"); 15 Console.WriteLine("KeyA removed? " + (cache.Get("keyA") == null).ToString()); 16 Console.WriteLine("We are done..."); 17 Console.ReadKey(); 18 }
三,CacheManager多級緩存配置
實際開發中,我們常常會需要使用多級緩存。
一種常見的情況是,你有一個分布式式緩存服務器,例如redis,獨立的緩存服務器能夠讓我們的多個系統應用程序都能夠共享這些緩存的數據,因為這些緩存項的創建是昂貴的。
和訪問數據庫相比,分布式緩存速度較快,但是和內存相比,還是不夠快。因為分布式緩存使用還需要序列化和網絡傳輸的時間消耗。
這個時候里,做個分級緩存是個好的解決方案,將內存緩存結合分布式緩存使用,使用頻率高的數據直接從內存中讀取,這將大大提高應用程序的整體性能。
使用內存緩存的讀取速度能夠達到分布式緩存的100倍,甚至更高。
使用CacheManager, 配置多級緩存是一件非常容易的事情
1 var cache = CacheFactory.Build<int>("myCache", settings => 2 { 3 settings 4 .WithSystemRuntimeCacheHandle("inProcessCache")//內存緩存Handle 5 .And 6 .WithRedisConfiguration("redis", config =>//Redis緩存配置 7 { 8 config.WithAllowAdmin() 9 .WithDatabase(0) 10 .WithEndpoint("localhost", 6379); 11 }) 12 .WithMaxRetries(1000)//嘗試次數 13 .WithRetryTimeout(100)//嘗試超時時間 14 .WithRedisBackPlate("redis")//redis使用Back Plate 15 .WithRedisCacheHandle("redis", true);//redis緩存handle 16 });
上面代碼中,內存緩存和Redis緩存配置部分很容易看明白。但是BackPlate是什么作用? 接下來,我們看看CacheManager中的BackPlate擋板機制。
四, BackPlate解決分布式緩存中的同步問題
對於大型的軟件系統,常常都是分為很多獨立的子項目,各個子項目為了節約成本或者是方便數據共享,常常會共用同一個分布緩存服務器。這樣在使用多級緩存的時候,就有可能出現數據不一致的情況。
假設在系統A中的更新了緩存中的一個數據項,這個時候CacheManager會在A設置的所有的緩存handle中更新改數據,這里也包括了分布式緩存上的數據。但是在系統B中的內存緩存中,還是會存在着舊的未更新的數據。當系統B從緩存中取這條記錄的時候,就會出現內存緩存和分布式緩存中的數據不一致的情況。
為了防止這一點,緩存管理器有一個功能叫做cachebackplate將嘗試同步多個系統中的緩存。
上面設置的多級緩存中,我們就將redis作為BackPlate的源. 也就是說所有的數據都需要以redis中緩存的數據為藍本。
在設置redis作為BackPlate之后,同樣發生上面的數據不一致的情況的時候,只要redis中的數據被修改了,就會觸發CacheManager更新所有系統中的內存緩存中的數據,和redis中的數據保持一致。
同步的工作是如何完成的?
每次一條緩存記錄被刪除或更新的時候,Cache Manager會發送一個消息,讓BackPlate存儲這次的數據變化信息。所有其它的系統將異步接收這些消息,並將相應地作出更新和刪除操作,保證數據的一致性。
五,ExpirationMode和CacheUpdateMode
涉及到緩存,就必然有緩存過期的問題。CacheManager中提供了一些簡單的緩存過期方式設置。
1 public enum ExpirationMode 2 { 3 None = 0, 4 Sliding = 1, 5 Absolute = 2, 6 }
同時CacheManager還為多級緩存之間設置不同的數據更新策略
1 public enum CacheUpdateMode 2 { 3 None = 0, 4 Full = 1, 5 Up = 2, 6 }
使用Sliding和Up, 我們我可以為多級緩存設置不同的緩存過期時間,這樣使用頻率高的數據就能夠保存在訪問速度更快的內存中,訪問頻率次高的放到分布式緩存中。當CacheManager在內存中找不到緩存數據的時候,就會嘗試在分布式緩存中找。找到后,根據Up設置,會再將該緩存數據保存到內存緩存中。
具體的配置方式如下:
1 var cache = CacheFactory.Build<int>("myCache", settings => 2 { 3 settings.WithUpdateMode(CacheUpdateMode.Up) 4 .WithSystemRuntimeCacheHandle("inProcessCache")//內存緩存Handle 5 .WithExpiration(ExpirationMode.Sliding, TimeSpan.FromSeconds(60))) 6 .And 7 .WithRedisConfiguration("redis", config =>//Redis緩存配置 8 { 9 config.WithAllowAdmin() 10 .WithDatabase(0) 11 .WithEndpoint("localhost", 6379); 12 }). 13 .WithExpiration(ExpirationMode.Sliding, TimeSpan. FromHours (24))) 14 .WithMaxRetries(1000)//嘗試次數 15 .WithRetryTimeout(100)//嘗試超時時間 16 .WithRedisBackPlate("redis")//redis使用Back Plate 17 .WithRedisCacheHandle("redis", true);//redis緩存handle 18 19 });
六,緩存使用分析
在緩存使用中,對於緩存hit和miss數據態比較關系,這些數據能夠幫助我們分析和調整緩存的設置,幫助緩存使用地更加合理。
1 var cache = CacheFactory.Build("cacheName", settings => settings 2 .WithSystemRuntimeCacheHandle("handleName") 3 .EnableStatistics() 4 .EnablePerformanceCounters());
在配置好緩存的Statistic功能后,我們就能夠跟蹤到緩存的使用情況了, 下面就是分別打印各個緩存handle中的分析數據。
1 foreach (var handle in cache.CacheHandles) 2 { 3 var stats = handle.Stats; 4 Console.WriteLine(string.Format( 5 "Items: {0}, Hits: {1}, Miss: {2}, Remove: {3}, ClearRegion: {4}, Clear: {5}, Adds: {6}, Puts: {7}, Gets: {8}", 6 stats.GetStatistic(CacheStatsCounterType.Items), 7 stats.GetStatistic(CacheStatsCounterType.Hits), 8 stats.GetStatistic(CacheStatsCounterType.Misses), 9 stats.GetStatistic(CacheStatsCounterType.RemoveCalls), 10 stats.GetStatistic(CacheStatsCounterType.ClearRegionCalls), 11 stats.GetStatistic(CacheStatsCounterType.ClearCalls), 12 stats.GetStatistic(CacheStatsCounterType.AddCalls), 13 stats.GetStatistic(CacheStatsCounterType.PutCalls), 14 stats.GetStatistic(CacheStatsCounterType.GetCalls) 15 )); 16 }
七,結尾
緩存是個好東西,用好了能夠極大的提高性能。緩存的使用本身是個很大的話題,這邊文章只是從緩存管理這個角度介紹了CachManager的使用。
下面是CacheManager相關的資料和鏈接:
官方主頁
源代碼
https://github.com/MichaCo/CacheManager
官方MVC項目的Sample
https://github.com/MichaCo/CacheManager/tree/master/samples/CacheManager.Samples.Mvc
最近在思考不同情況下緩存使用的區別問題。對於互聯網項目來說,數據的一致性要求常常不太高,緩存管理中,關注點可能在緩存的命中率上。對於應用系統,訪問請求不大,但是對於數據的一致性要求較高,緩存中的數據更新策略可能更加重要。
怎樣才是好的適合應用系統的緩存設計呢? 如果大家有興趣,歡迎探討指教。