1、前言html
一、上篇.NET Core ResponseCache【緩存篇(一)】中咱們提到了使用客戶端緩存、和服務端緩存。本文咱們介紹MemoryCache緩存組件,說到服務端緩存咱們通常都會想到MemoryCache、Redis等等優秀的緩存組件,各自有各自使用的場景。MemoryCache的類型比較單一是Object對象存儲、Redis的數據類型就相對比較多 String(字符串),List(列表),set(去重集合),zset(去重排序集合),hash(哈希)。還有HyperLogLog,bitMap,GeoHash,BloomFilter這四種尚未詳細瞭解,等下篇講解Redis的時候詳細給各位姥爺供上。github
2、MemoryCache緩存組件使用緩存
一、首先咱們須要將MemoryCache組件注入到程序中。安全
public void ConfigureServices(IServiceCollection services) { //添加內存緩存 services.AddMemoryCache(); }
二、使用的方法也比較簡單和咱們日常寫代碼差很少。在控制其中注入基類IMemoryCache,看到下面這個基類,我還覺得會只有兩個方法。多線程
#region 注入緩存 private readonly IMemoryCache Cache; public MemoryCacheController(IMemoryCache cache) { Cache = cache; } #endregion
三、當我正常使用的時候發現方法原來使用推展方法寫在這裏。使用起來很簡單我就直接代碼了。函數
四、設置緩存主要是四種過時時間。post
1:永久不過時 就是老子就是不過時氣死你。this
2:絕對過時時間 這個就比如咱們買的安眠藥,2020年07月22日過時。那你今天就要好好利用這個時間了。url
3:相對過時時間 這個比如你吃飯三個小時,肚子就呱呱叫了,相對於當前時間延長。
4:滑動過時時間 這個就是相對過時時間的升級版,咱們每隔三個小時就會肚子餓,那麼咱們是否是能夠等到兩個半小時就是吃飯,肚子就不會叫了。這樣在必定時間內使用這個緩存就會在你最後使用緩存的時間上延長。
//設置緩存 當咱們沒有指定過時時間,理論是永久的(後面會說到不理論的) Cache.Set("key", "value"); Console.WriteLine(Cache.Get("key")); //設置緩存絕對過時時間 Cache.Set("key1", "value", new DateTimeOffset(new DateTime(2020, 7, 22))); Console.WriteLine(Cache.Get("key1")); //設置相對過時時間 Cache.Set("key2", "value", new TimeSpan(0, 0, 10)); Console.WriteLine(Cache.Get("key2")); //設置滑動過時時間 Cache.Set("key3", "value", new MemoryCacheEntryOptions() { //設置滑動過時 SlidingExpiration = new TimeSpan(0, 0, 5), //設置緩存的優先級,當咱們緩存空間不足的時候會移除等級低的緩存,以此類推(清除的時候不會管是否過時) //Low, 低的意思 //Normal, 正常模式 默認模式 //High, 高 //NeverRemove 毫不回收,這個就算內存滿了也不會清除。 Priority = CacheItemPriority.NeverRemove }); Console.WriteLine(Cache.Get("key3"));
五、這裏咱們還要講一下緩存失效機制,這裏它不是主動失效的,只有當咱們再次對緩存MemoryCache組件進行增刪查改的時候纔會掃描裏面的內存是否存在過時的,進行垃圾回收。這裏咱們使用GetOrCreate方法建立咱們的緩存證明咱們的說法。這個能夠設置過時回調函數。圖下咱們能夠看到當咱們的緩存過時以後,就沒有對緩存進行操做了就不會有回調函數觸發。
Cache.GetOrCreate("key4", cacheEntry => { //設置滑動過時 cacheEntry.SlidingExpiration= new TimeSpan(0, 0, 5); //設置刪除回調函數 cacheEntry.RegisterPostEvictionCallback(CallbackFunction); //設置內存 return cacheEntry.Value = "滑動過時時間帶刪除回調"; }); Thread.Sleep(2000); Console.WriteLine(Cache.Get("key4")); Thread.Sleep(4000); Console.WriteLine(Cache.Get("key4")); Thread.Sleep(5000);
六、可是咱們將最後一句代碼解開封印。這裏有一個問題,當咱們執行等待了5秒鐘按道理過時了,可是沒有觸發刪除回調,我截圖上的是我將代碼塊拉到了以前執行的最後一行代碼纔有,這個我也蒙了。可是當我第二次進來訪問就有回調函數了。
/// <summary> /// 刪除回調函數 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="reason"></param> /// <param name="state"></param> public void CallbackFunction(object key, object value, EvictionReason reason, object state) { Console.WriteLine($"你被刪除了key:{key},value:{value}回調函數"); }
七、CreateEntry設置緩存,有一點特殊正常設置緩存的時候,獲取的時候是null,只有當咱們執行Dispose或者using的時候才成功。帶着這個疑問咱們決定看看源碼。
////CreateEntry設置緩存 var entity = Cache.CreateEntry("key5"); entity.Value = "5555"; Console.WriteLine("CreateEntry 獲取信息:" + Cache.Get("key5")); //結果null
//方法1 var entity = Cache.CreateEntry("key5"); entity.Value = "5555"; entity.Dispose(); Console.WriteLine("CreateEntry 獲取信息:" + Cache.Get("key5")); //結果5555 //方法2 using (var entity = Cache.CreateEntry("key5")) { entity.Value = "5555"; Console.WriteLine("CreateEntry 獲取信息:" + Cache.Get("key5")); //結果5555 }
八、CacheEntry有一個重要的方法Dispose(),由於它繼承IDisposable,在Dispost方法中調用了_notifyCacheEntryDisposed委託。
public void Dispose() { if (!_added) { _added = true; _scope.Dispose(); _notifyCacheEntryDisposed(this);//在此調用委託,而此委託是被MemoryCache類中的SetEntry賦值。目的是將CacheEntry實體放入MemoryCache類的字典中,也就是放入緩存中。 PropagateOptions(CacheEntryHelper.Current);
3、MemoryCache組件源碼
一、首先看咱們在Startup中聲明添加的內存緩存的對應的源碼。 services.AddMemoryCache();能夠看到這裏是將緩存組件使用單例模式注入咱們程序中。
/// <summary> /// Adds a non distributed in memory implementation of <see cref="IMemoryCache"/> to the /// <see cref="IServiceCollection" />. /// </summary> /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection AddMemoryCache(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } services.AddOptions(); services.TryAdd(ServiceDescriptor.Singleton<IMemoryCache, MemoryCache>()); return services; }
二、MemoryCacheOptions類:主要是配置一些參數
ExpirationScanFrequency:此字段代表隔一段時間掃描緩存,移除過時緩存,默認頻率爲一分鐘。
SizeLimit:設置緩存的大小。
CompactionPercentage:壓縮比例 默認爲百分之五。
//添加內存緩存 services.AddMemoryCache(x=> { x.CompactionPercentage = 0.05;//緩存壓縮大小 x.ExpirationScanFrequency= new TimeSpan(0, 0, 1); //默認自動掃描時間爲三分鐘 x.SizeLimit = 500 * 1024 * 1024; //大小爲500M });
三、MemoryCache類
1:ConcurrentDictionary<object, CacheEntry> _entries:一個多線程安全的字典類型,其實緩存的本質就是這個字典,將全部緩存都放入這個字典中,而後經過字典的key(字典的key其實和緩存實體CacheEntry的key值同樣)獲取CacheEntry實體(CacheEntry實體包含key和value,也就是咱們代碼中設置的key和value)。
2:_expirationScanFrequency:Span類型,表示掃描過時緩存的頻率時間,此字段的值來自MemoryCacheOptions類的ExpirationScanFrequency。須要注意的是:假如設置爲一分鐘掃描一次緩存集合,這個掃描不是主動掃描的,只有當對緩存集合操做時纔會掃描。好比增刪改查緩存集合的時候,會判斷上一次掃描的時間離如今過去多久了,若是超過掃描設置的時間,纔會掃描。
private void StartScanForExpiredItems()//掃描函數。在添加、查找、刪除、修改緩存的時候,會調用此方法掃描過濾過時緩存,並非主動隔一段時間執行 { var now = _clock.UtcNow; if (_expirationScanFrequency < now - _lastExpirationScan)//判斷如今的時間和最後一次掃描時間,是否是大於設置的時間段 { _lastExpirationScan = now; Task.Factory.StartNew(state => ScanForExpiredItems((MemoryCache)state), this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } } private static void ScanForExpiredItems(MemoryCache cache) { var now = cache._clock.UtcNow; foreach (var entry in cache._entries.Values)//而後遍歷字典集合,移除過時緩存 { if (entry.CheckExpired(now)) { cache.RemoveEntry(entry); } } }