明白了緩存穿透和緩存雪崩,再瞭解一下緩存擊穿!

緩存擊穿和緩存雪崩的區別在於:雪崩針對不少 key,而擊穿只針對於某一個熱點 key。web

設置緩存永不過時,這個方法雖然很暴力,可是確實能解決大部分的問題,固然,大部分場景也不太適用;數據庫

設置隨機過時時間,這個方案對於緩存擊穿來講就不太適用了,由於擊穿只針對一個熱點 key,只要它一失效,大量的訪問就會擊垮數據庫;緩存

其他的方案好比使用互斥鎖、雙緩存機制,也均可以解決緩存擊穿的問題,讓咱們看看這些方案的具體實現。app

主動刷新緩存

緩存設置成永不過時,在更新或刪除 DB 中的數據時,也主動地把緩存中的數據更新或刪除掉。spa

這個方案很容易理解,可是實現起來卻很複雜,但凡須要使用緩存的數據,都須要增長雙寫數據庫和緩存的代碼,而且雙寫過程當中,還須要保持數據一致性。線程

檢查更新

緩存依然保持設置過時時間,每次 get 緩存的時候,都和數據的過時時間和當前時間進行一下對比,當間隔時間小於一個閾值的時候,主動更新緩存。code

好比(緩存過時時間 - 當前系統時間)小於 5 分鐘,那麼就刷新一次緩存,而且重置緩存過時時間;orm

不過這個方法也有個致命的問題:若是一個數據,剛好在緩存失效前五分鐘,一次訪問都沒有,那麼就不會觸發檢查更新,當緩存失效後有大量請求訪問,那麼也會形成緩存擊穿。cdn

使用鎖

在緩存失效後,經過互斥鎖或者隊列,控制讀數據庫和寫緩存的線程數量。blog

第一種方法:整個方法是 synchronized 的,這樣作雖然能夠防止大量請求落到 DB 上,可是就算是緩存沒有失效,須要從 DB 中查詢數據也須要排隊,無疑是下降了系統的吞吐量。

public synchronized String getCacheData() {
      String cacheData = "";
      //讀 Redis
      cacheData = getDataFromRedis();
      if (cacheData.isEmpty()) {
          //讀數據庫
          cacheData = getDataFromDB();
          //寫 Redis
          setDataToCache(cacheData);
      }
      return cacheData;
}
複製代碼

第二種方法:當緩存失效時,只對查詢數據庫的操做進行加鎖,這樣對於緩存沒有失效的狀況也很是友好,可是查詢操做這裏加鎖,也只是會阻塞掉住其餘調用,第一其餘線程要等待,對調用方不友好,第二這些請求被阻塞的請求最終仍是會落到 DB 上的。

static Object lock = new Object();

public String getCacheData() {
      String cacheData = "";
      // 讀 Redis
      cacheData = getDataFromRedis();
      if (cacheData.isEmpty()) {
          synchronized (lock) {
              //讀數據庫
           cacheData = getDataFromDB();
              //寫 Redis
              setDataToCache(cacheData);
          }
      }
      return cacheData;
 }
複製代碼

第三種方法:使用互斥鎖,搶到鎖的話讀數據庫並寫入緩存,搶不到鎖的話也不阻塞,而是直接去讀緩存,若是緩存中依然讀不到數據(搶到鎖的可能尚未將緩存寫入成功),就等一會再試試讀緩存。

public String getCacheData(){
   String result = "";
      //讀 Redis
      result = getDataFromRedis();
      if (result.isEmpty()) {
          if (reenLock.tryLock()) {
              try {
                  //讀數據庫
                  result = getDataFromDB();
                  //寫 Redis
                  setDataToCache(result);
              }catch(Exception e){
               //...
              }finally {
                  reenLock.unlock();//釋放鎖
              }
          } else {
                //注意:這裏能夠結合下文中的雙緩存機制:
                //搶不到鎖的去查詢二級緩存
              //讀 Redis
              result = getDataFromRedis();
              if (result.isEmpty()) {
                  try {
                    Thread.sleep(100);
                  } catch (InterruptedException e) {
                  //...
              }
                  return getCacheData();
              }
          }
      }
      return result;
}
複製代碼

雙緩存

設置一級緩存和二級緩存,一級緩存過時時間短,二級緩存過時時間長或者不過時,一級緩存失效後訪問二級緩存,同時刷新一級緩存和二級緩存。

雙緩存的方式,說白了就是不能將一級緩存和二級緩存中數據同時變成失效,當一級緩存失效後,有多個請求訪問,彼此之間依然是競爭鎖,搶到鎖的線程查詢數據庫並刷新緩存,而其餘沒有搶到鎖的線程,直接訪問二級緩存(代碼能夠參考上文中的互斥鎖),如圖:

雙緩存機制
雙緩存機制

會點代碼的大叔 | 文【原創】


@會點代碼的大叔
@會點代碼的大叔
相關文章
相關標籤/搜索