.Net Core緩存組件(MemoryCache)【緩存篇(二)】


一、前言

  .Net Core緩存源碼

  1、上篇.NET Core ResponseCache【緩存篇(一)】中我們提到了使用客戶端緩存、和服務端緩存。本文我們介紹MemoryCache緩存組件,說到服務端緩存我們一般都會想到MemoryCache、Redis等等優秀的緩存組件,各自有各自使用的場景。MemoryCache的類型比較單一是Object對象存儲、Redis的數據類型就相對比較多 String(字符串),List(列表),set(去重集合),zset(去重排序集合),hash(哈希)。還有HyperLogLog,bitMap,GeoHash,BloomFilter這四種還沒有詳細了解,等下篇講解Redis的時候詳細給各位姥爺供上。

二、MemoryCache緩存組件使用

  1、首先我們需要將MemoryCache組件注入到程序中。

 public void ConfigureServices(IServiceCollection services)
        {
            //添加內存緩存
            services.AddMemoryCache();
        }

  2、使用的方法也比較簡單和我們平常寫代碼差不多。在控制其中注入基類IMemoryCache,看到下面這個基類,我還以為會只有兩個方法。

 #region 注入緩存
        private readonly IMemoryCache Cache;
        public MemoryCacheController(IMemoryCache cache)
        {
            Cache = cache;
        }
        #endregion

   3、當我正常使用的時候發現方法原來使用推展方法寫在這里。使用起來很簡單我就直接代碼了。

  4、設置緩存主要是四種過期時間。

      1:永久不過期 就是老子就是不過期氣死你。

       2:絕對過期時間 這個就好比我們買的安眠葯,2020年07月22日過期。那你今天就要好好利用這個時間了。

      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"));

  5、這里我們還要講一下緩存失效機制,這里它不是主動失效的,只有當我們再次對緩存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);

   6、但是我們將最后一句代碼解開封印。這里有一個問題,當我們執行等待了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}回調函數");
        }

  7、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
            }

  8、CacheEntry有一個重要的方法Dispose(),因為它繼承IDisposable,在Dispost方法中調用了_notifyCacheEntryDisposed委托。

public void Dispose()
{
      if (!_added)
      {
          _added = true;
          _scope.Dispose();
          _notifyCacheEntryDisposed(this);//在此調用委托,而此委托是被MemoryCache類中的SetEntry賦值。目的是將CacheEntry實體放入MemoryCache類的字典中,也就是放入緩存中。
          PropagateOptions(CacheEntryHelper.Current);
      

三、MemoryCache組件源碼

  1、首先看我們在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;
        }

  2、MemoryCacheOptions類:主要是配置一些參數

    ExpirationScanFrequency:此字段表明隔一段時間掃描緩存,移除過期緩存,默認頻率為一分鍾。

    SizeLimit:設置緩存的大小。

    CompactionPercentage:壓縮比例 默認為百分之五。

            //添加內存緩存
            services.AddMemoryCache(x=> {
                x.CompactionPercentage = 0.05;//緩存壓縮大小
                x.ExpirationScanFrequency= new TimeSpan(0, 0, 1); //默認自動掃描時間為一分鍾
                x.SizeLimit = 500 * 1024 * 1024; //大小為500M
            });

  3、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);
                }
            }
        }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM