redis緩存存在的隱患及其解決方案


redis緩存
1.緩存穿透
1>.什么是緩存穿透?
業務系統需要查訓的數據根本不存在,當業務系統查詢時,
首先會在緩存中查訓,由於緩存中不存在,然后在往數據
庫中查,由於該數據在數據庫中也不存在,數據庫返回為空。

綜上所述:業務系統訪問數據庫中不存在的數據陳偉緩存穿透。
2>.緩存穿透的危害:
海量請求同一條數據庫中不存在的數據,這些請求不經過緩存,
直接訪問數據庫,數據庫壓力劇增,業務系統中屬IO最為脆弱,
這種危害可能會導致系統奔潰。
3>.為什么會發生緩存穿透?
(1).惡意攻擊,故意制造大量不存在的數據,破壞整個系統。
(2).代碼邏輯錯誤。
4>.解決方案:
(1).緩存空的數據:
redis以鍵值對存儲數據,當第一請求數據時,數據不存在,將數據庫返回的
的結果為空儲存在指定的健中,后續發送請求時直接相應客戶端數據不存在,
無需再次查詢數據庫。
(2).緩存空數據存在兩個問題:
<!>.空值做了緩存,以為這緩存中要存更多的健,需要占用更多的內存空間,
如果是攻擊,問題會更加嚴重,應該給這個健設置一個過期時間,讓他自動刪除。
<2>.緩存層和存儲層的數據會有一段時間窗口不一致,會對業務有一定的影響
比如設置5分鍾過期,如果緩存層添加這個數據,有一段時間就會出現與數據庫不一致,
此時就利用消息系統或者其他方式清除緩存層的空對象
(3).布隆過濾器:
在緩存層再添加一層障礙,布隆過濾器中存儲目前數據庫所存在的所有key
當業務系統請求查訓時,首先在布隆過濾器中查找key是否存在,若不存在,則說明
數據庫中沒有該條數據,因此緩存就不要查了,直接返回空對象給客戶端,
若存在則進入緩存中查訓,如果沒有再查數據庫。

這個方式是用於數據命中率不高,數據相對固定穩定時性低(通常數據集較大)
的應用場景,代碼維護復雜,但緩存占用空間較少。
(4).兩種方案比教:
對於惡意攻擊,查訓的key往往不同,而且數據較多,此時,第一種方案比較合適,因為
它存儲所有空數據的Key,對惡意攻擊的key往往不相同,而且每個key往往只執行一次,
而不在使用第二次,但它保護不了數據庫。
對空數據的的key各不相同,key重復請求依據場合而言,應該選用第二種方案,對於空數據的
key數量有限,key重復請求依據場合而言,應該選用第一種。2.緩存雪崩:

1>.什么是緩存雪崩?
如果緩存因某種原因發生宕機,或者存在緩存中的數據大面積的是失效,原本
緩存抵擋的海量查訓全部用涌向緩存庫,因而導致整個系統崩潰。
2>.如何避免緩存雪崩:
(!).將緩存中的數據失效時間錯開,過期時間做一個均勻分布的處理。
(2).排斥鎖:第一個線程來讀取數據,緩存中沒有,先訪問數據庫,后續線程
再過來訪問就必須等待第一個線程訪問數據庫成功后,再從緩存中訪問。
(3)使用分布式鎖,這當然是考慮到在分布式環境下,讀請求會落到集群中的不同應用服務機器上。分布式鎖可以選用zookeeper或基於redis的setnx這類原子性操作來實現。
加鎖時需要用到經典的double-check lock。
本方案雖然能夠減輕DB壓力,防止雪崩。但由於用到了加鎖排隊,吞吐率是不高的。僅適用於並發量不大的場景。
3.緩存擊穿:
1>.什么是熱點數據集中失效?
緩存中的每一條數據到會設置失效時間,過了時效時間,該數據就會自動在緩存中刪除,
從而保證數據的一致性。
但是,對於一些請求量極高的熱點數據,一旦過了失效后海量請求最終會落到數據庫上,
從而導致數據庫壓力極大,系統奔潰

如果第一個線程請求緩存時,緩存中不存在,因而去查訊數據庫,就在第一個線程查訓數據,而數據庫尚未返回查詢結果是,
后續線程持續請求,緩存中沒有數據,這些請求都會查訓數據庫,給數據庫造成壓力,
其次,這些線程持續查詢完畢后,都會重復更新緩存。
4.緩存雪崩解決方案:
緩存雪崩是由於原有緩存失效(過期),新緩存未到期間。所有請求都去查詢數據庫,而對數據庫CPU和內存造成巨大壓力,嚴重的會
造成數據庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。
  1. 碰到這種情況,一般並發量不是特別多的時候,使用最多的解決方案是加鎖排隊。
public object GetProductListNew(){
  const int cacheTime = 30;
  const string cacheKey = "product_list";
  const string lockKey = cacheKey;
  var cacheValue = CacheHelper.Get(cacheKey);
  if (cacheValue != null){
    return cacheValue;
  }else{
    lock (lockKey)
  {
    cacheValue = CacheHelper.Get(cacheKey);
    if (cacheValue != null){
      return cacheValue;
    }else{

        cacheValue = GetProductListFromDB(); //這里一般是 sql查詢數據。
        CacheHelper.Add(cacheKey, cacheValue, cacheTime);

      }

    }

  }
    return cacheValue;
}
2. 加鎖排隊只是為了減輕數據庫的壓力,並沒有提高系統吞吐量。假設在高並發下,緩存重建期間key是鎖着的,這是過來1000個請求
999個都在阻塞的。同樣會導致用戶等待超時,這是個治標不治本的方法。
  還有一個解決辦法解決方案是:給每一個緩存數據增加相應的緩存標記,記錄緩存的是否失效,如果緩存標記失效,則更新數據緩
存。
public object GetProductListNew(){
     int cacheTime = 30;
    string cacheKey = "product_list";
   //緩存標記。
   string cacheSign = cacheKey + "_sign";
   //獲取緩存標記
   var sign = CacheHelper.Get(cacheSign);
   //獲取緩存值
   var cacheValue = CacheHelper.Get(cacheKey);
   if (sign != null)
  {
    return cacheValue; //未過期,直接返回。
  }
 else
 {
   //緩存標記過期后重新給緩存標記隨便給值,然后緩存時間為30分鍾
   CacheHelper.Add(cacheSign, "1", cacheTime);
  cacheValue = GetProductListFromDB(); //這里一般是 sql查詢數據。
  CacheHelper.Add(cacheKey, cacheValue, cacheTime*2); //日期設緩存時間的2倍,用於臟讀。
  return cacheValue;
}
}


免責聲明!

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



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