系列

2018年3月27日 星期二

C# Cache 用法介紹及實作測試


C# Cache 介紹 及實作測試





初衷
最近因為在web api 效能上遇到了一些瓶頸,就去尋求簡單可提升效能,就想著可以把一些設定檔及不常異動的資訊放入cache內,可以來降低IO和資料庫的負擔,然後發現其實我對cache非常的陌生,就想說來練習和整理紀錄一下此次所學習的東西,順便練習看一下MSDN(因為我國文太差所以對我來說是文言文等級的)。

結論
Cache這個東西感覺就像是火焰(沒有形體有難以追蹤),有足夠的了解及安全事項有做好,應該是很好的幫手可以拿來取暖、煮飯、燒熱水洗澡,可是如果隨便使用就跟在程式中點了一把火一樣,一不注意就........燃燒吧~火鳥~把程式全部燒光光吧~


Cache練習 Source Code(GitHub): https://github.com/ZeroTiem/CacheExercise


Cache的選擇
我在 .NET Cache 資訊時非常的頭大,因為好像有很多種,對於要用哪一種讓我傷透腦筋,所以我就簡單的查詢一些資訊來做個簡單的差異表,看看差別在哪裡應該怎麼選擇。

.NET快取差異對照表

Cache
MemoryCache 
ObjectCache
MDSN
實作 Web 應用程式的快取。 這個類別無法被繼承。
命名空間:   System.Web.Caching
組件:  System.Web (於 System.Web.dll)

繼承階層
  System.Web.Caching.Cache
表示可實作記憶體內部快取的類型。
命名空間:   System.Runtime.Caching
組件:  System.Runtime.Caching (於 System.Runtime.Caching.dll)

繼承階層
    System.Runtime.Caching.MemoryCache
表示物件快取,並提供基底方法和屬性存取物件快取。
命名空間:   System.Runtime.Caching
組件:  System.Runtime.Caching (於 System.Runtime.Caching.dll)

繼承階層
  System.Runtime.Caching.ObjectCache
整理重點
  • .NET WEB應用程式
  • .NET Framework 3.5 或之前版本為目標
  • 只能存在記憶體中可/集中式
  • 任何.NET 應用程式
  • .NET Framework 4.0 或之後版本為目標
  • 能存在記憶體中以外的地方/分散式
  • 任何.NET 應用程式
  • .NET Framework 4.0 或之後版本為目標
  • 能存在記憶體中以外的地方/分散式
個人理解

版本比較舊只能支援在WEB 應用程式,因為只能存在本機的記憶體中,所以無法支援分散式架構。
新版的快取機制可以支援在所有.NET 程式中,需要自制一些快取機制可以用他來寫,因為可以儲存於本機外的記憶體所以也支援分散式架構。
提供標準的介面來統一快取機制的操做,所以他只是抽象的介面底層還是實作MemoryCache,也因為如此MemoryCache有的特性他也有,也提供了很多方法可以使用 。(也因為底層實作是MemoryCache所以應該也會支援分散架構)。


參考資訊:



ObjectCache 說明

看起來NET 4.0 之後都推薦這個方法所以就.......就是你了皮卡邱( 大誤),ObjectCache提供標準的介面來統一快取機制的操做,所以他只是抽象的介面底層還是實作MemoryCache,也因為如此MemoryCache有的特性他也有,也提供了很多方法可以使用 。(也因為底層實作是MemoryCache所以應該也會支援分散架構)。

MSND說明 :內建 MemoryCache 類別衍生自 ObjectCache 類別。 MemoryCache 類別是在只有具象物件快取實作 .NET Framework 4 衍生自 ObjectCache 類別。(參考資訊:https://msdn.microsoft.com/zh-tw/library/system.runtime.caching.objectcache(v=vs.110).aspx)



ObjectCache 新增

注意的事項(新增注意事項是我直接引用別人部落格的資訊,我也有貼上引用來源,如果有侵權的話麻煩告知我,我會拿下來)
1. 加入快取 (Set, Add, AddOrGetExisting)
Set (快取已存在時,直接覆寫)
Cache[key]=value  (快取已存在時,直接覆寫,但無法設定 CacheItemPolicy)
Add (快取已存在時,不會覆寫原有設定,會回傳false結果告知新增失敗)
引用來源:https://dotblogs.com.tw/wasichris/2015/11/14/153922(個人覺得這篇寫的非常的棒,其實可以直接看他這篇比較清楚)


ObjectCache  設定快取回收規則
類型
方法
說明
範例
CacheItemPolicy
AbsoluteExpiration
設定多久時間後cache被回收
  ObjectCache cache2 = MemoryCache.Default;
            var policy = new CacheItemPolicy();//設定回收時間
            //多久時間清除快取項目
            policy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(15.0);
            cache2.Set("test", "testing", policy);
CacheItemPolicy
SlidingExpiration
多久時間沒有使用cache就回收
ObjectCache cache2 = MemoryCache.Default;
            //設定回收時間
            var policy = new CacheItemPolicy();
            //多久時間cache沒有被使用就回收
            policy.SlidingExpiration = TimeSpan.FromSeconds(5);
            cache2.Set("test", "testing", policy);
CacheItemPolicy
NotRemovable
(未接受任務)
(未接受任務)


ObjectCache  其他
類型
方法
說明
範例
MemoryCache
取得電腦上可供快取使用的記憶體數量(以位元組為單位)。
ObjectCache cache2 = MemoryCache.Default;
var cacheMemoryLimit = (cache2 as MemoryCache).CacheMemoryLimit;
MemoryCache
快取可使用的實體記憶體百分比。
ObjectCache cache2 = MemoryCache.Default;
var PhysicalMemoryLimit = (cache2 as MemoryCache).PhysicalMemoryLimit ;


ObjectCache  設定預設值
屬性
描述
CacheMemoryLimitMegabytes
MemoryCache 物件執行個體可以成長的最大記憶體大小 (MB)。 預設值為 0,表示預設會使用 MemoryCache 類別的自動調整啟發學習法。
Name
快取組態的名稱。
PhysicalMemoryLimitPercentage
快取可使用的實體記憶體百分比。 預設值為 0,表示預設會使用MemoryCache 類別的自動調整啟發學習法。
PollingInterval
表示時間間隔的值,在此時間之後,快取實作會比較目前的記憶體負載與針對快取執行個體所設定的絕對和百分比型記憶體限制。 此值是以 "HH:MM:SS" 格式輸入。


範例
<system.runtime.caching>
<memoryCache>
<namedCaches>
<add name="Default"
cacheMemoryLimitMegabytes="10"
physicalMemoryLimitPercentage="0"
pollingInterval="00:05:00" />
</namedCaches>
</memoryCache>
</system.runtime.caching>


實驗室列表

程式名稱
說明
實驗結果
ConsoleApp_Cache_AbsoluteExpiration
AbsoluteExpiration 多久時間後cache被回收
成功
ConsoleApp_Cache_SlidingExpiration 
SlidingExpiration 多久時間沒有使用cache就回收
成功
ConsoleApp_Cache_SetConfig
WebConfig 設定 Cache 然後使用 CacheMemoryLimit 及 PhysicalMemoryLimit來觀察是變化
失敗
ConsoleApp_Cache_Remove
Cache_Remove 當資料異動(移除)時使用自訂方法處理移除資訊
成功
實驗 Source Code(GitHub): https://github.com/ZeroTiem/CacheExercise


實驗結果

說明:AbsoluteExpiration 多久時間後cache被回收
程式名稱:ConsoleApp_Cache_AbsoluteExpiration
Main:
           
ObjectCache cache2 = MemoryCache.Default;
            var policy = new CacheItemPolicy();//設定回收時間
            policy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(15.0);//多久時間清除快取項目
            cache2.Set("test", "testing", policy);
            int WhileGo = 20;
            int i = 1;
            while (i <= WhileGo)
            {
                Thread.Sleep(1000);//延遲1000ms,也就是1秒
                Console.WriteLine("while Go " + i + " " + DateTime.Now + " cacheValue:" + cache2["test"]);
                i++;
            }
            Console.WriteLine("\n" + DateTime.Now.ToString() + "  " + cache2["test"]);
            Console.WriteLine("\n" + "End");
範例是簡單的程式,先設定當MemoryCache 15秒後就會回收,可以看到測試結果在前14次cacheValue值都還存在,那為什麼第15次並沒有出現呢?推測原因是因為我是使用Thread.Sleep(1000)這個在延遲上還要再加上程式運行的時間在第15次執行時就已經超過了15秒了,所以在第15次就已經被回收掉了。 


======================================================
說明:SlidingExpiration 多久時間沒有使用cache就回收
程式名稱:ConsoleApp_Cache_SlidingExpiration 
Main:           
ObjectCache cache2 = MemoryCache.Default;
            //設定回收時間
            var policy = new CacheItemPolicy();
            //多久時間cache沒有被使用就回收
            policy.SlidingExpiration = TimeSpan.FromSeconds(5);
            cache2.Set("test", "testing", policy);
            var goSecs = new List<int> { 4, 8, 12, 19 };
            int WhileGo = 30;
            int i = 1;
            while (i <= WhileGo)
            {
                Thread.Sleep(1000);//延遲1000ms,也就是1秒
                if (goSecs.Contains(i))
                {
                    Console.WriteLine("while Go " + i + " " + DateTime.Now + " cacheValue:" + cache2["test"]);
                }
                i++;
            }
            Console.WriteLine("\n" + "End");

執行結果
範例是簡單的程式,先設定當MemoryCache 在 5 秒內沒有被使用的話,就會回收,所以在時間 5 秒內有執行使用到都會存在,在最後一次執行我特別把秒數拉長到 7 秒就可以看到 cacheValue 沒有值了,因為超過了5秒 已經被回收了,值也就不存在了。 



==============================================================
說明:WebConfig 設定 Default 然後使用 CacheMemoryLimit 及 PhysicalMemoryLimit 來觀察是否有成功設定到 Cache 的設定值(失敗)
程式名稱:ConsoleApp_Cache_SetConfig
Main:
          
Console.WriteLine("===ConsoleApp_Cache_SetConfig===");
            ObjectCache cache2 = MemoryCache.Default;
            var physicalMemoryLimit = (cache2 as MemoryCache).PhysicalMemoryLimit;
            Console.WriteLine("physicalMemoryLimit:" + physicalMemoryLimit + "%");
            var cacheMemoryLimit = (cache2 as MemoryCache).CacheMemoryLimit;
            Console.WriteLine("cacheMemoryLimit:" + Convert.ToDouble(cacheMemoryLimit) + "Byte");
            Console.WriteLine("End");

[config 設定值1]
          
  <?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
</configuration>
設定
輸出
  • 可用記憶體百分比為 99%
  • 可用記憶體大小為 1887436800Byte 

[config 設定值2]
           
<system.runtime.caching>
    <memoryCache>
      <namedCaches>
        <add name="Default"
             cacheMemoryLimitMegabytes="0"
             physicalMemoryLimitPercentage="0"
             />
      </namedCaches>
    </memoryCache>
  </system.runtime.caching>
設定
  • 可用記憶體百分比為 0% PS(設定0等於自動調整)
  • 可用記憶體大小為 0 (最小單位是MB)(設定0等於自動調整)
輸出
  • 可用記憶體百分比為 99%
  • 可用記憶體大小為 1887436800Byte 



[config 設定值3] 無解參考
        
    <system.runtime.caching>
    <memoryCache>
      <namedCaches>
        <add name="Default"
             cacheMemoryLimitMegabytes="1"
             physicalMemoryLimitPercentage="1"
             />
      </namedCaches>
    </memoryCache>
  </system.runtime.caching>
設定
  • 可用記憶體百分比為 1%
  • 可用記憶體大小為 1MB (最小單位是MB)
輸出
  • 可用記憶體百分比為 3%
  • 可用記憶體大小為 1048576Byte = 1MB 
這是什麼詭異的狀況呢?我明明就是設定1%記憶體阿~怎麼變成3%了呢?
當兩個值有衝突時取其低值嗎? 
EX. 共 99GB 可用記憶體資源 , 設定[可成長記憶體上限百分比 80%] 及 [可成長記憶體上限 70GB] 這樣會已 70GB 為主嗎?是否有什麼方法可以監控測試呢? 



似乎是答案
引用:
The first thing you might notice is that it doesn't even try to look at the size of the cache until after a Gen2 garbage collection, instead just falling back on the existing stored size value in cacheSizeSamples. So you won't ever be able to hit the target right on, but if the rest worked we would at least get a size measurement before we got in real trouble.
So assuming a Gen2 GC has occurred, we run into problem 2, which is that ref2.ApproximateSize does a horrible job of actually approximating the size of the cache. Slogging through CLR junk I found that this is a System.SizedReference, and this is what it's doing to get the value (IntPtr is a handle to the MemoryCache object itself):

翻譯:
您可能會注意到的第一件事是,它甚至沒有嘗試直到Gen2垃圾回收之後才查看緩存的大小,而是回退到cacheSizeSamples中的現有存儲大小值。所以你永遠不可能直接擊中目標,但是如果剩下的工作起來,我們至少可以在我們遇到麻煩之前進行尺寸測量。 因此,假設發生了Gen2 GC,我們遇到了問題2,即ref2.ApproximateSize在真正接近緩存大小方面做了一件糟糕的工作。通過CLR垃圾進行阻塞我發現這是一個System.SizedReference,這是它獲取值的過程(IntPtr是MemoryCache對象本身的句柄)


==============================================================
說明:Cache_Remove 當資料異動(移除)時使用自訂方法處理移除資訊
程式名稱:ConsoleApp_Cache_Remove
Main:
static void Main(string[] args)
        {
            ////=================多久時間回收測試=======================
            ObjectCache cache = MemoryCache.Default;
            //設定回收時間
            var policy = new CacheItemPolicy();
            //多久時間cache沒有被使用就回收
            policy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(3.0);//多久時間清除快取項目
            policy.RemovedCallback = CacheItemRemoved;//如果資訊有移除要做什麼事情
            object objectvalue = "Data";//要存儲的資訊
            cache.Set("test", objectvalue, policy);//設定cache並且給予值
            int WhileGo = 30;
            int i = 1;
            while (i <= WhileGo)
            {
                Thread.Sleep(1000);//延遲1000ms,也就是1秒
                Console.WriteLine("while Go " + i + " " + DateTime.Now + "  cacheValue:" + cache["test"]);
                i++;
            }
            Console.WriteLine("\n" + "End");
        }
        static void CacheItemRemoved(CacheEntryRemovedArguments arguments)
        {
            // The arguments object contains information about the removed item such  as:
            var key = arguments.CacheItem.Key;
            var removedObject = arguments.CacheItem.Value;
            var removedReason = arguments.RemovedReason;
            Console.WriteLine($"[Run cacheItemRemoved] key:{ key } / removedObject:{  removedObject } / removedReason:{ removedReason }");
        }

先設定當MemoryCache 在 3秒內沒有被使用的話MemoryCache就會被回收,當被回收時會觸發自訂的程序並且取得被回收的名稱、資料、移除資訊
PS.這裡我也有測試關閉程式並不會觸發RemovedCallback  

所有參考資訊
參考資訊:http://www.wenwenti.info/article/102965(設定config)
參考資訊:在WPF應用程序中緩存應用程序數據
DeteTime & DateTimeOffset:https://msdn.microsoft.com/zh-tw/library/bb384267(v=vs.110).aspx

沒有留言:

張貼留言