第一次接觸到Cache的時候,是在WebForm中,第一次接觸,我就再也沒能忘記,cache(擦車,的拼音) 客戶端瀏覽器緩存https://blog.csdn.net/y874961524/article/details/61419716 CDN緩存原理https://www.cnblogs.co ...
第一次接觸到Cache的時候,是在WebForm中,第一次接觸,我就再也沒能忘記,cache(擦車,的拼音)
客戶端瀏覽器緩存https://blog.csdn.net/y874961524/article/details/61419716
CDN緩存原理https://www.cnblogs.com/shijingxiang/articles/5179032.html
阿裡雲CDN開啟設置https://jingyan.baidu.com/article/948f5924f1d642d80ff5f980.html
有句話叫做,系統性能優化的第一步,就是使用緩存,所以,緩存真的很重要
緩存:
實際上是一種效果&目標,就是獲取數據點時候,第一次獲取之後找個地方存起來,後面直接用,這樣一來可以提升後面每次獲取數據的效率。讀取配置文件的時候把信息放在靜態欄位,這個就是緩存。緩存是無處不在的。
我們來請求一個網站,打開開發人員工具
客戶端緩存的好處:
1、縮短網路路徑,加快響應速度
2、減少請求,降低伺服器壓力
瀏覽器緩存究竟是怎麼做到的?
打開一個網頁,瀏覽器-----請求---伺服器---處理請求會發響應------瀏覽器展示
Http協議,數據傳輸的格式(協議,就像是兩人交流,都用什麼語言)
信息是否緩存,一定是伺服器控制的。ResponseHeader--Cache---Control來指定下緩存策略,瀏覽器看到了這個,就去存儲一下。
第一次請求伺服器:
再一次請求伺服器
DNS是互聯網的第一跳,DNS緩存就是CDN,內容分髮網絡,CDN就是加速緩存的
沒有用CDN的請求:
使用了CDN緩存
反向代理:
1、隔離網路,保護伺服器(節約公共IP)
2、網路加速,反向代理雙網卡
3、負載均衡
4、緩存(跟CDN,也是識別一下header,壓縮到一個物理路徑/記憶體)
為什麼叫反向代理?因為他就是一個代理,一般的代理,是客戶端和伺服器之間,有一個代理,去做處理的。但是這個代理是安裝在伺服器端的。
幾種緩存套路相同,但是位置不同,影響的範圍也不同。
客戶端緩存:隻影響當前用戶
CDN緩存:針對一批用戶
反向代理緩存:針對全部用戶。
客戶端緩存,存在記憶體或者硬碟,下次直接用。Cookie,存在記憶體或者硬碟,瀏覽器每次請求伺服器都會帶上的信息。
什麼時候用緩存?
1、重覆請求,100人訪問首頁,每個人其實做的都一樣,不就是重覆
2、耗時好資源
3、結果沒變的
下麵有一個第三方數據存儲和獲取的地方:
/// <summary> /// 第三方數據存儲和獲取的地方 /// </summary> public class CustomCache { /// <summary> /// private:私有一下數據容器,安全 /// static:不被GC /// 字典:讀寫效率高 /// </summary> private static Dictionary<string, object> CustomCacheDictionary = new Dictionary<string, object>(); public static void Add(string key, object oVaule) { CustomCacheDictionary.Add(key, oVaule); } /// <summary> /// 要求在Get前做Exists檢測 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public static T Get<T>(string key) { return (T)CustomCacheDictionary[key]; } public static bool Exists(string key) { return CustomCacheDictionary.ContainsKey(key); } public static T GetT<T>(string key, Func<T> func) { T t = default(T); if (!CustomCache.Exists(key)) { t = func.Invoke(); CustomCache.Add(key, t); } else { t = CustomCache.Get<T>(key); } return t; } }
存取數據的唯一標識:1 唯一的 2 能重現
for (int i = 0; i < 5; i++) { Console.WriteLine($"獲取{nameof(DBHelper)} {i}次 {DateTime.Now.ToString("yyyyMMdd HHmmss.fff")}"); //List<Program> programList = DBHelper.Query<Program>(123); List<Program> programList = null; string key = $"{nameof(DBHelper)}_Query_{123}"; //存取數據的唯一標識:1 唯一的 2 能重現 //if (!CustomCache.Exists(key)) //{ // programList = DBHelper.Query<Program>(123); // CustomCache.Add(key, programList); //} //else //{ // programList = CustomCache.Get<List<Program>>(key); //} programList = CustomCache.GetT<List<Program>>(key, () => DBHelper.Query<Program>(123)); }
/// <summary> /// 資料庫查詢 /// </summary> public class DBHelper { /// <summary> /// 1 耗時耗資源 /// 2 參數固定時,結果不變 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="index"></param> /// <returns></returns> public static List<T> Query<T>(int index) { Console.WriteLine("This is {0} Query", typeof(DBHelper)); long lResult = 0; for (int i = index; i < 1000000000; i++) { lResult += i; } List<T> tList = new List<T>(); for (int i = 0; i < index % 3; i++) { tList.Add(default(T)); } return tList; } }
緩存優化性能,核心就是結果重用,下次請求還是上一次的結果。如果資料庫中有變化,豈不是用了一個錯誤的數據?是的,緩存是難免的,緩存難免會有臟數據,當然了,我們也會分門別類的去儘量減少臟數據。
用戶--角色--菜單,用戶許可權查的多+比較耗資源+相對穩定,非常適合緩存,緩存方式應該是用戶id為key,菜單列表作為value。
string name = "bingle"; List<string> menu = new List<string>(); if (!CustomCache.Exists(name)) { menu = new List<string>() { "123", "125553", "143", "123456" }; CustomCache.Add(name, menu); } else { menu = CustomCache.Get<List<string>>(name); }
假如bingle的許可權變化了,緩存應該失效。數據更新影響單挑緩存,常規做法是Remove而不是更新,因為緩存只是用來提升效率的,而不是數據保存的,因此不需要更新,只需要刪除就好,如果真的下次用上了,到時候再去初始化。
CustomCache類增加刪除緩存的方法:
public static void Remove(string key) { CustomCacheDictionary.Remove(key); }
string name = "bingle"; CustomCache.Remove(name); List<string> menu = new List<string>(); if (!CustomCache.Exists(name)) { menu = new List<string>() { "123", "125553", "143" }; CustomCache.Add(name, menu); } else { menu = CustomCache.Get<List<string>>(name); }
刪除了某個菜單,影響了一大批用戶。根據菜單--昭覺寺---找用戶---每一個拼裝key然後去Remove(最準確)。但是這種方式不行,為了緩存增加資料庫的任務,最大的問題是數據量的問題,緩存是二八原則,只有20%的熱點用戶才緩存,這樣做的成本太高。
可以選擇加上一個RemoveAll的方法
public static void RemoveAll() { CustomCacheDictionary.Clear(); }
或者,菜單刪除了,能不能隻影響一部分的緩存數據呢?
1、添加緩存時,key帶上規則,比如許可權包含_menu_
2、清理時,就只刪除key含_menu_的
/// <summary> /// 按條件刪除 /// </summary> /// <param name="func"></param> public static void RemoveCondition(Func<string, bool> func) { List<string> keyList = new List<string>(); lock (CustomCache_Lock) foreach (var key in CustomCacheDictionary.Keys) { if (func.Invoke(key)) { keyList.Add(key); } } keyList.ForEach(s => Remove(s)); }
第三方修改了數據,緩存並不知道,這個就沒辦法了
a 可以調用介面清理緩存,b系統修改數據,調用c西永通知下緩存更新,b就只能容忍了,容忍臟數據,但是可以加上時間限制,減少影響時間。
時間,過期策略:
永久有效----目前就是
絕對過期:
有個時間點,超過就過期了
滑動過期:
多久之後過期,如果期間更新/查詢/檢查存在,就再次延長多久。
/// <summary> /// 主動清理 /// </summary> static CustomCache() { Task.Run(() => { while (true) { try { List<string> keyList = new List<string>(); lock (CustomCache_Lock) { foreach (var key in CustomCacheDictionary.Keys) { DataModel model = (DataModel)CustomCacheDictionary[key]; if (model.ObsloteType != ObsloteType.Never && model.DeadLine < DateTime.Now) { keyList.Add(key); } } keyList.ForEach(s => Remove(s)); } Thread.Sleep(1000 * 60 * 10); } catch (Exception ex) { Console.WriteLine(ex.Message); continue; } } }); }
多線程問題:
List<Task> taskList = new List<Task>(); for (int i = 0; i < 110000; i++) { int k = i; taskList.Add(Task.Run(() => CustomCache.Add($"TestKey_{k}", $"TestValue_{k}", 10))); } for (int i = 0; i < 100; i++) { int k = i; taskList.Add(Task.Run(() => CustomCache.Remove($"TestKey_{k}"))); } for (int i = 0; i < 100; i++) { int k = i; taskList.Add(Task.Run(() => CustomCache.Exists($"TestKey_{k}"))); } //Thread.Sleep(10*1000); Task.WaitAll(taskList.ToArray());
多線程操作非現場安全的容器,會造成衝突
1、線程安全容器ConcurrentDictionary
2、用lock---Add/Remove/遍歷,可以解決問題,但是性能呢?
怎麼降低影響,提升性能呢?多個數據容器,多個鎖,容器之間可以併發
為瞭解決多線程問題,CustomCache 類最終修改成如下:
public class CustomCache { //ConcurrentDictionary private static readonly object CustomCache_Lock = new object(); /// <summary> /// 主動清理 /// </summary> static CustomCache() { Task.Run(() => { while (true) { try { List<string> keyList = new List<string>(); lock (CustomCache_Lock) { foreach (var key in CustomCacheDictionary.Keys) { DataModel model = (DataModel)CustomCacheDictionary[key]; if (model.ObsloteType != ObsloteType.Never && model.DeadLine < DateTime.Now) { keyList.Add(key); } } keyList.ForEach(s => Remove(s)); } Thread.Sleep(1000 * 60 * 10); } catch (Exception ex) { Console.WriteLine(ex.Message); continue; } } }); } /// <summary> /// private:私有一下數據容器,安全 /// static:不被GC /// 字典:讀寫效率高 /// </summary> //private static Dictionary<string, object> CustomCacheDictionary = new Dictionary<string, object>(); private static Dictionary<string, object> CustomCacheDictionary = new Dictionary<string, object>(); public static void Add(string key, object oVaule) { lock (CustomCache_Lock) CustomCacheDictionary.Add(key, new DataModel() { Value = oVaule, ObsloteType = ObsloteType.Never, }); } /// <summary> /// 絕對過期 /// </summary> /// <param name="key"></param> /// <param name="oVaule"></param> /// <param name="timeOutSecond"></param> public static void Add(string key, object oVaule, int timeOutSecond) { lock (CustomCache_Lock) CustomCacheDictionary.Add(key, new DataModel() { Value = oVaule, ObsloteType = ObsloteType.Absolutely, DeadLine = DateTime.Now.AddSeconds(timeOutSecond) }); } /// <summary> /// 相對過期 /// </summary> /// <param name="key"></param> /// <param name="oVaule"></param> /// <param name="duration"></param> public static void Add(string key, object oVaule, TimeSpan duration) { lock (CustomCache_Lock) CustomCacheDictionary.Add(key, new DataModel() { Value = oVaule, ObsloteType = ObsloteType.Relative, DeadLine = DateTime.Now.Add(duration), Duration = duration }); } /// <summary> /// 要求在Get前做Exists檢測 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <returns></returns> public static T Get<T>(string key) { return (T)(((DataModel)CustomCacheDictionary[key]).Value); } /// <summary> /// 被動清理,請求了數據,才能清理 /// </summary> /// <param name="key"></param> /// <returns></returns> public static bool Exists(string key) { if (CustomCacheDictionary.ContainsKey(key)) { DataModel model = (DataModel)CustomCacheDictionary[key]; if (model.ObsloteType == ObsloteType.Never) { return true; } else if (model.DeadLine < DateTime.Now)//現在已經超過你的最後時間 { lock (CustomCache_Lock) CustomCacheDictionary.Remove(key); return false; } else { if (model.ObsloteType == ObsloteType.Relative)//沒有過期&是滑動 所以要更新 { model.DeadLine = DateTime.Now.Add(model.Duration); } return true; } } else { return false; } } /// <summary> /// 刪除key /// </summary> /// <param name="key"></param> public static void Remove(string key) { lock (CustomCache_Lock) CustomCacheDictionary.Remove(key); } public static void RemoveAll() { lock (CustomCache_Lock) CustomCacheDictionary.Clear(); } /// <summary> /// 按條件刪除 /// </summary> /// <param name="func"></param> public static void RemoveCondition(Func<string, bool> func) { List<string> keyList = new List<string>(); lock (CustomCache_Lock) foreach (var key in CustomCacheDictionary.Keys) { if (func.Invoke(key)) { keyList.Add(key); } } keyList.ForEach(s => Remove(s)); } public static T GetT<T>(string key, Func<T> func) { T t = default(T); if (!CustomCache.Exists(key)) { t = func.Invoke(); CustomCache.Add(key, t); } else { t = CustomCache.Get<T>(key); } return t; } } /// <summary> /// 緩存的信息 /// </summary> internal class DataModel { public object Value { get; set; } public ObsloteType ObsloteType { get; set; } public DateTime DeadLine { get; set; } public TimeSpan Duration { get; set; } //數據清理後出發事件 public event Action DataClearEvent; } public enum ObsloteType { Never, Absolutely, Relative }View Code
public class CustomCacheNew { //動態初始化多個容器和多個鎖 private static int CPUNumer = 0;//獲取系統的CPU數 private static List<Dictionary<string, object>> DictionaryList = new List<Dictionary<string, object>>(); private static List<object> LockList = new List<object>(); static CustomCacheNew() { CPUNumer = 4; for (int i = 0; i < CPUNumer; i++) { DictionaryList.Add(new Dictionary<string, object>()); LockList.Add(new object()); } Task.Run(() => { while (true) { Thread.Sleep(1000 * 60 * 10); try { for (int i = 0; i < CPUNumer; i++) { List<string> keyList = new List<string>(); lock (LockList[i])//減少鎖的影響範圍 { foreach (var key in DictionaryList[i].Keys) { DataModel model = (DataModel)DictionaryList[i][key]; if (model.ObsloteType != ObsloteType.Never && model.DeadLine < DateTime.Now) { keyList.Add(key); } } keyList.ForEach(s => DictionaryList[i].Remove(s)); } } } catch (Exception ex) { Console.WriteLine(ex.Message); continue; } } }); }View Code
緩存究竟哪裡用?滿足哪些特點適合用緩存?
1、訪問頻繁
2、耗時耗資源
3、相對穩定
4、體積不那麼大的
不是說嚴格滿足,具體的還要看情況,存一次能查三次,就值得緩存(大型想換標準)
下麵應該用緩存
1、字典數據
2、省市區
3、配置文件
4、網站公告信息
5、部門許可權,菜單許可權
6、熱搜
7、類別列表/產品列表
8、用戶,其實Session也是緩存的一種表現
股票信息價格/彩票開獎信息,這些不能用緩存,即時性要求很高。圖片/視頻,這些也不行,太大了。商品評論,這個可以用緩存的,雖然評論彙編,但是這個不重要,我們不一定非要看到最新的,而且第一頁一般不變。
可以測試下CustomCache的性能,十萬/百萬/千萬 插入/獲取/刪除的性能。