緩存cache(擦車)


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



 


免責聲明!

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



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