1. 程式人生 > >關於快取雪崩和快取穿透等問題

關於快取雪崩和快取穿透等問題

快取雪崩

  快取雪崩是由於原有快取失效(過期),新快取未到期間。所有請求都去查詢資料庫,而對資料庫CPU和記憶體造成巨大壓力,嚴重的會造成資料庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。

  (1) 碰到這種情況,一般併發量不是特別多的時候,使用最多的解決方案是加鎖排隊。

  1. public object GetProductListNew()  
  2.         {  
  3.             constint cacheTime = 30;  
  4.             const string cacheKey = "product_list";  
  5.             const
     string lockKey = cacheKey;  
  6.             var cacheValue = CacheHelper.Get(cacheKey);  
  7.             if (cacheValue != null)  
  8.             {  
  9.                 return cacheValue;  
  10.             }  
  11.             else
  12.             {  
  13.                 lock (lockKey)  
  14.                 {  
  15.                     cacheValue = CacheHelper.Get(cacheKey);  
  16.                     if (cacheValue != null)  
  17.                     {  
  18.                         return cacheValue;  
  19.                     }  
  20.                     else
  21.                     {  
  22.                         cacheValue = GetProductListFromDB(); //這裡一般是 sql查詢資料。              
  23.                         CacheHelper.Add(cacheKey, cacheValue, cacheTime);  
  24.                     }                      
  25.                 }  
  26.                 return cacheValue;  
  27.             }  
  28.         }  
(2) 加鎖排隊只是為了減輕資料庫的壓力,並沒有提高系統吞吐量。假設在高併發下,快取重建期間key是鎖著的,這是過來1000個請求999個都在阻塞的。同樣會導致使用者等待超時,這是個治標不治本的方法。
 還有一個解決辦法解決方案是:給每一個快取資料增加相應的快取標記,記錄快取的是否失效,如果快取標記失效,則更新資料快取。
  1. public object GetProductListNew()  
  2.         {  
  3.             constint cacheTime = 30;  
  4.             const string cacheKey = "product_list";  
  5.             //快取標記。
  6.             const string cacheSign = cacheKey + "_sign";  
  7.             var sign = CacheHelper.Get(cacheSign);  
  8.             //獲取快取值
  9.             var cacheValue = CacheHelper.Get(cacheKey);  
  10.             if (sign != null)  
  11.             {  
  12.                 return cacheValue; //未過期,直接返回。
  13.             }  
  14.             else
  15.             {  
  16.                 CacheHelper.Add(cacheSign, "1", cacheTime);  
  17.                 ThreadPool.QueueUserWorkItem((arg) =>  
  18.                 {  
  19.                     cacheValue = GetProductListFromDB(); //這裡一般是 sql查詢資料。
  20.                     CacheHelper.Add(cacheKey, cacheValue, cacheTime*2); //日期設快取時間的2倍,用於髒讀。                
  21.                 });  
  22.                 return cacheValue;  
  23.             }  
  24.         }  

快取標記

記錄快取資料是否過期,如果過期會觸發通知另外的執行緒在後臺去更新實際key的快取。

快取資料

它的過期時間比快取標記的時間延長1倍,例:標記快取時間30分鐘,資料快取設定為60分鐘。 這樣,當快取標記key過期後,實際快取還能把舊資料返回給呼叫端,直到另外的執行緒在後臺更新完成後,才會返回新快取。這樣做後,就可以一定程度上提高系統吞吐量。

快取穿透
  快取穿透是指使用者查詢資料,在資料庫沒有,自然在快取中也不會有。這樣就導致使用者查詢的時候,在快取中找不到,每次都要去資料庫再查詢一遍,然後返回空。這樣請求就繞過快取直接查資料庫,這也是經常提的快取命中率問題。
  解決的辦法就是:如果查詢資料庫也為空,直接設定一個預設值存放到快取,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問資料庫,這種辦法最簡單粗暴。

  1. public object GetProductListNew()  
  2.         {  
  3.             constint cacheTime = 30;  
  4.             const string cacheKey = "product_list";  
  5.             var cacheValue = CacheHelper.Get(cacheKey);  
  6.             if (cacheValue != null)  
  7.                 return cacheValue;  
  8.             cacheValue = CacheHelper.Get(cacheKey);  
  9.             if (cacheValue != null)  
  10.             {  
  11.                 return cacheValue;  
  12.             }  
  13.             else
  14.             {  
  15.                 cacheValue = GetProductListFromDB(); //資料庫查詢不到,為空。
  16.                 if (cacheValue == null)  
  17.                 {  
  18.                     cacheValue = string.Empty; //如果發現為空,設定個預設值,也快取起來。                
  19.                 }  
  20.                 CacheHelper.Add(cacheKey, cacheValue, cacheTime);  
  21.                 return cacheValue;  
  22.             }  
  23.         }  

把空結果,也給快取起來,這樣下次同樣的請求就可以直接返回空了,即可以避免當查詢的值為空時引起的快取穿透。同時也可以單獨設定個快取區域儲存空值,對要查詢的key進行預先校驗,然後再放行給後面的正常快取處理邏輯。


快取預熱
  快取預熱就是系統上線後,將相關的快取資料直接載入到快取系統。這樣避免,使用者請求的時候,再去載入相關的資料。
解決思路:
(1)直接寫個快取重新整理頁面,上線時手工操作下。
(2)資料量不大,可以在WEB系統啟動的時候載入。
(3)定時重新整理快取,

快取更新
快取淘汰的策略有兩種:
  (1)定時去清理過期的快取。
  (2)當有使用者請求過來時,再判斷這個請求所用到的快取是否過期,過期的話就去底層系統得到新資料並更新快取。 
  兩者各有優劣,第一種的缺點是維護大量快取的key是比較麻煩的,第二種的缺點就是每次使用者請求過來都要判斷快取失效,邏輯相對比較複雜,具體用哪種方案,大家可以根據自己的應用場景來權衡。1. 預估失效時間 2. 版本號(必須單調遞增,時間戳是最好的選擇)3. 提供手動清理快取的介面。