文章目錄
使用 .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"); }
更新 cache
我把執行 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>;
}
}
其他測試
多個 proprty 下會不會造成 block ?
不會
測試流程
加入新的 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"); }
將兩個 property sleep 時間設一長一短
- RefreshTable2Data sleep(10000)
- RefreshTableData sleep(3000)
先起一個 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
心得
修改的工非常少(只在 getter 中加上 lock),倒是花在建立測試情境的時間比較長,雖然程式碼不優,還是解決了問題,後續就來修改成使用泛型的版本以配合多個 property
參考資料
文章作者 Yowko Tsai
上次更新 2021-08-18
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。