緩存cache(擦車)

来源:https://www.cnblogs.com/taotaozhuanyong/archive/2019/09/21/11565162.html
-Advertisement-
Play Games

第一次接觸到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的性能,十萬/百萬/千萬  插入/獲取/刪除的性能。



 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ZooKeeper技術的極少以及ZooKeeper集群的搭建 ...
  • 0. 序 我從一生下來就呆在這個昏暗的地方。 我不明白為什麼程式員這麼喜歡 Dark Mode,Brighten Mode 才是我的最愛。聽說最近連 iphone 都開始支持 Dark Mode 了,沒話講。。。說好的絕不妥協呢? 我周圍是熙熙攘攘的函數群,穿插著變數聲明和巨集定義。 在我們這裡,函數 ...
  • 一、前言 應聘IC前端相關崗位時,FIFO是最常考也是最基本的題目。FIFO經常用於數據緩存、位寬轉換、非同步時鐘域處理。隨著晶元規模的快速增長,靈活的system verilog成為設計/驗證人員的基本功。本文從簡易版的同步FIFO開始,熟悉IP設計與驗證的基礎技能。 二、IP設計 FIFO這一IP ...
  • 背景 運維人員反饋一個容器化的java程式每跑一段時間就會出現OOM問題,重啟後,間隔大概兩天後復現。 問題調查 一查日誌 由於是容器化部署的程式,登上主機後使用docker logs ContainerId查看輸出日誌,並沒有發現任何異常輸出。 使用docker stats查看容器使用的資源情況, ...
  • #set指令 #set指令用於向一個變數或者對象賦值。 格式: #set($var = value) LHS是一個變數,不要使用特殊字元例如英文句號等,不能用大括弧括起來。測試發現#set($user.name = 'zhangsan'),#set(${age} = 18)均賦值失敗。 RHS可以是 ...
  • 前言 說實話,這章本來不打算講的,因為配置多數據源的網上有很多類似的教程。但是最近因為項目要用到分庫分表,所以讓我研究一下看怎麼實現。我想著上一篇博客講了多環境的配置,不同的環境調用不同的資料庫,那接下來就將一個環境用到多個庫也就講了。所以才有了這篇文章。 我們先來看一下今天項目的項目結構,在上篇博 ...
  • 最近做了一些.NET Core的程式,有在Windows下運行的 有在CentOS 下運行的,Windows下運行的還好,對Windows下還算比較熟悉了,但CentOS 下 每次都是找筆記支持命令 於是今天晚上就乾脆把以.NET Core程式已服務形式啟動的代碼封裝了下,代碼 主要是便於安裝。 我 ...
  • Excel文件讀、寫可以使用Office自帶的庫(Microsoft.Office.Interop.Excel),前提是本機須安裝office才能運行,且不同的office版本之間可能會有相容問題。還可以使用NPOI,在不安裝office的時候也是可以讀寫的,速度很快。當然,還有支持Excel200 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...