2017-01-31

使用 .NET Framework 內建的 MemoryCache 來 Cache 常用資料 - Part 2 使用 lock 避免 ddos db

經過前一篇文章 使用 .NET Framework 內建的 MemoryCache 來 Cache 常用資料 - Part 1 極簡做法 介紹了最簡單達到 cache 資料的方法,文末也提到已知的問題,首先先解決程式可能 ddos db 的重大缺失


前情提要

  • CacheHelper
    public static class CacheHelper
        {
            private static MemoryCache _cache = MemoryCache.Default;
            /// <summary>
            ///  TableData
            /// </summary>
            public static List<Table> TableData
            {
                get
                {
                    if (!_cache.Contains("TableData"))
                        RefreshTableData();
                    return _cache.Get("TableData") as List<Table>;
                }
            }
    
            /// <summary>
            /// 更新 TableData
            /// </summary>
            public static void RefreshTableData()
            {
                //移除 cache 中資料
                _cache.Remove("TableData");
    
                //存取資料
                TestEnumEntities db = new TestEnumEntities();
                var listAgency = db.Table.Select(a=>a.WeekDay).ToList();
    
                //設定 cache 過期時間
                CacheItemPolicy cacheItemPolicy = new CacheItemPolicy() {AbsoluteExpiration = DateTime.Now.AddDays(1) };
                //加入 cache
                _cache.Add("TableData", listAgency, cacheItemPolicy);
            }
        }
    

測試程式

  • 修改自黑大文章 改良式GetCachableData可快取查詢函式

    void Main()
    {
     //先清除 cache 中資料
     MemoryCache _cache = MemoryCache.Default;
     _cache.Remove("TableData");
     //起三個 thread
     var tasks = new List<Task>();
     for (var i = 0; i < 3; i++)
     {
      tasks.Add(Task.Factory.StartNew(() =>
      {
       var data = CacheHelper.TableData;
       Console.WriteLine("Data:" + JsonConvert.SerializeObject(data));
      }));
     }
     tasks.ForEach(t => t.Wait());
     Console.WriteLine("Done");
    }
    

    1runtriple

  • 我把執行 sleep 跟 輸出訊息的部份搬到執行更新 cache 的方法中以便於測試

    /// <summary>
    /// 更新 TableData
    /// </summary>
    public static void RefreshTableData()
    {
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} Start Job,Now:{DateTime.Now}");
        Thread.Sleep(3000);
    
        //移除 cache 中資料
        _cache.Remove("TableData");
    
        //存取資料
        TestEnumEntities db = new TestEnumEntities();
        var listAgency = db.Table.ToList();
    
        //設定 cache 過期時間
        CacheItemPolicy cacheItemPolicy = new CacheItemPolicy() { AbsoluteExpiration = DateTime.Now.AddDays(1) };
        //加入 cache
        _cache.Add("TableData", listAgency, cacheItemPolicy);
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} Stop Job,Now:{DateTime.Now}");
        Console.WriteLine("OK");
    
    }
    

加入 lock

    /// <summary>
    ///  TableData
    /// </summary>
    public static List<Table> TableData
    {
        get
        {
            MemoryCache _cache = MemoryCache.Default;
            //加上 lock
            lock (_cache)
            {
                if (!_cache.Contains("TableData"))
                {
                    RefreshTableData();
                }
            }
            return _cache.Get("TableData") as List<Table>;
        }
    }

2runonce

其他測試

  • 多個 proprty 下會不會造成 block ? --> 不會
  • 測試流程
    • 1.加入新的 property 及更新方法至 cachehelper

      public static List<Table2> Table2Data
      {
          get
          {
      
              lock (_cache)
              {
                  if (!_cache.Contains("Table2Data"))
                  {
                      RefreshTable2Data();
                  }
              }
              return _cache.Get("Table2Data") as List<Table2>;
          }
      }
      /// <summary>
      /// 更新 TableData
      /// </summary>
      public static void RefreshTable2Data()
      {
          Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} Start Job,Now:{DateTime.Now}");
          Thread.Sleep(10000);
      
          //移除 cache 中資料
          _cache.Remove("Table2Data");
      
          //存取資料
          TestEnumEntities db = new TestEnumEntities();
          var listAgency = db.Table2.Select(a => a.TestStr).ToList();
      
          //設定 cache 過期時間
          CacheItemPolicy cacheItemPolicy = new CacheItemPolicy() { AbsoluteExpiration = DateTime.Now.AddDays(1) };
          //加入 cache
          _cache.Add("Table2Data", listAgency, cacheItemPolicy);
          Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} Stop Job,Now:{DateTime.Now}");
          Console.WriteLine("OK");
      }
      
    • 2.將兩個 property sleep 時間設一長一短

      • RefreshTable2Data sleep(10000)
      • RefreshTableData sleep(3000)
    • 3.先起一個 thread 執行 sleep 時間較長的 property 更新方法,再起另一個 thread 執行 sleep 時間較短的 property 更新方法

      void Main()
      {
          MemoryCache _cache = MemoryCache.Default;
          _cache.Remove("TableData");
          _cache.Remove("Table2Data");
      
          // sleep 時間較久先跑
          Task.Run(() => CacheHelper.RefreshTable2Data());
          Task.Run( () => CacheHelper.RefreshTableData());
      
          Console.WriteLine("Done");
      }
      
    • 測試結果

      • sleep 時間短的還是先完成,沒有被另一個 thread block

        3mutipleproperty

心得

修改的工非常少(只在 getter 中加上 lock),倒是花在建立測試情境的時間比較長,雖然程式碼不優,還是解決了問題,後續就來修改成使用泛型的版本以配合多個 property

參考資料

  1. MemoryCache 方法
  2. 改良式GetCachableData可快取查詢函式

沒有留言:

張貼留言