緩存擊穿的解決方案


一.什么樣的數據適合緩存?


這里寫圖片描述

二.什么是緩存擊穿


這里寫圖片描述

三.緩存擊穿的解決辦法

方案一

   后台定義一個job(定時任務)專門主動更新緩存數據.比如,一個緩存中的數據過期時間是30分鍾,那么job每隔29分鍾定時刷新數據(將從數據庫中查到的數據更新到緩存中).

  • 這種方案比較容易理解,但會增加系統復雜度。比較適合那些 key 相對固定,cache 粒度較大的業務,key 比較分散的則不太適合,實現起來也比較復雜。

方案二

     將緩存key的過期時間(絕對時間)一起保存到緩存中(可以拼接,可以添加新字段,可以采用單獨的key保存..不管用什么方式,只要兩者建立好關聯關系就行).在每次執行get操作后,都將get出來的緩存過期時間與當前系統時間做一個對比,如果緩存過期時間-當前系統時間<=1分鍾(自定義的一個值),則主動更新緩存.這樣就能保證緩存中的數據始終是最新的(和方案一一樣,讓數據不過期.)

  • 這種方案在特殊情況下也會有問題。假設緩存過期時間是12:00,而 11:59 
    到 12:00這 1 分鍾時間里恰好沒有 get 請求過來,又恰好請求都在 11:30 分的時 
    候高並發過來,那就悲劇了。這種情況比較極端,但並不是沒有可能。因為“高 
    並發”也可能是階段性在某個時間點爆發。

方案三

   采用 L1 (一級緩存)和 L2(二級緩存) 緩存方式,L1 緩存失效時間短,L2 緩存失效時間長。 請求優先從 L1 緩存獲取數據,如果 L1緩存未命中則加鎖,只有 1 個線程獲取到鎖,這個線程再從數據庫中讀取數據並將數據再更新到到 L1 緩存和 L2 緩存中,而其他線程依舊從 L2 緩存獲取數據並返回。

  • 這種方式,主要是通過避免緩存同時失效並結合鎖機制實現。所以,當數據更 
    新時,只能淘汰 L1 緩存,不能同時將 L1 和 L2 中的緩存同時淘汰。L2 緩存中 
    可能會存在臟數據,需要業務能夠容忍這種短時間的不一致。而且,這種方案 
    可能會造成額外的緩存空間浪費。

方案四

 加鎖

方法1

1

  1. // 方法1:
  2.  
    public synchronized List<String> getData01() {
  3.  
    List<String> result = new ArrayList<String>();
  4.  
    // 從緩存讀取數據
  5.  
    result = getDataFromCache();
  6.  
    if (result.isEmpty()) {
  7.  
    // 從數據庫查詢數據
  8.  
    result = getDataFromDB();
  9.  
    // 將查詢到的數據寫入緩存
  10.  
    setDataToCache(result);
  11.  
    }
  12.  
    return result;
  13.  
    }

 

  • 這種方式確實能夠防止緩存失效時高並發到數據庫,但是緩存沒有失效的時候,在從緩存中拿數據時需要排隊取鎖,這必然會大大的降低了系統的吞吐量.
方法2
    1. static Object lock = new Object();
    2.  
       
    3.  
      public List<String> getData02() {
    4.  
      List<String> result = new ArrayList<String>();
    5.  
      // 從緩存讀取數據
    6.  
      result = getDataFromCache();
    7.  
      if (result.isEmpty()) {
    8.  
      synchronized ( lock) {
    9.  
      // 從數據庫查詢數據
    10.  
      result = getDataFromDB();
    11.  
      // 將查詢到的數據寫入緩存
    12.  
      setDataToCache(result);
    13.  
      }
    14.  
      }
    15.  
      return result;
    16.  
      }
  • 這個方法在緩存命中的時候,系統的吞吐量不會受影響,但是當緩存失效時,請求還是會打到數據庫,只不過不是高並發而是阻塞而已.但是,這樣會造成用戶體驗不佳,並且還給數據庫帶來額外壓力.
方法3
  1. public List<String> getData03() {
  2.  
    List<String> result = new ArrayList<String>();
  3.  
    // 從緩存讀取數據
  4.  
    result = getDataFromCache();
  5.  
    if (result.isEmpty()) {
  6.  
    synchronized (lock) {
  7.  
    //雙重判斷,第二個以及之后的請求不必去找數據庫,直接命中緩存
  8.  
    // 查詢緩存
  9.  
    result = getDataFromCache();
  10.  
    if (result.isEmpty()) {
  11.  
    // 從數據庫查詢數據
  12.  
    result = getDataFromDB();
  13.  
    // 將查詢到的數據寫入緩存
  14.  
    setDataToCache(result);
  15.  
    }
  16.  
    }
  17.  
    }
  18.  
    return result;
  19.  
    }
  • 1

 

方法4
    1. static Lock reenLock = new ReentrantLock();
    2.  
       
    3.  
      public List<String> getData04() throws InterruptedException {
    4.  
      List<String> result = new ArrayList<String>();
    5.  
      // 從緩存讀取數據
    6.  
      result = getDataFromCache();
    7.  
      if (result.isEmpty()) {
    8.  
      if (reenLock.tryLock()) {
    9.  
      try {
    10.  
      System. out.println("我拿到鎖了,從DB獲取數據庫后寫入緩存");
    11.  
      // 從數據庫查詢數據
    12.  
      result = getDataFromDB();
    13.  
      // 將查詢到的數據寫入緩存
    14.  
      setDataToCache(result);
    15.  
      } finally {
    16.  
      reenLock.unlock(); // 釋放鎖
    17.  
      }
    18.  
       
    19.  
      } else {
    20.  
      result = getDataFromCache(); // 先查一下緩存
    21.  
      if (result.isEmpty()) {
    22.  
      System. out.println("我沒拿到鎖,緩存也沒數據,先小憩一下");
    23.  
      Thread.sleep( 100);// 小憩一會兒
    24.  
      return getData04();// 重試
    25.  
      }
    26.  
      }
    27.  
      }
    28.  
      return result;
    29.  
      }
  • 最后使用互斥鎖的方式來實現,可以有效避免前面幾種問題.

 


免責聲明!

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



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