1. 程式人生 > >快取穿透和快取失效的預防和解決

快取穿透和快取失效的預防和解決

一.快取穿透:

     快取穿透是指查詢一個一定不存在的資料,由於快取是不命中時需要從資料庫查詢,查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到資料庫去查詢,造成快取穿透。

     解決辦法:

     1.布隆過濾

  對所有可能查詢的引數以hash形式儲存,在控制層先進行校驗,不符合則丟棄。還有最常見的則是採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被這個bitmap攔截掉,從而避免了對底層儲存系統的查詢壓力。

  補充:

      Bloom filter

  適用範圍:可以用來實現資料字典,進行資料的判重,或者集合求交集

  基本原理及要點:對於原理來說很簡單,位陣列+k個獨立hash函式。將hash函式對應的值的位陣列置1,查詢時如果發現所有hash函式對應位都是1說明存在,很明顯這個過程並不保證查詢的結果是100%正確的。同時也不支援刪除一個已經插入的關鍵字,因為該關鍵字對應的位會牽動到其他的關鍵字。所以一個簡單的改進就是counting Bloom filter,用一個counter陣列代替位陣列,就可以支援刪除了。新增時增加計數器,刪除時減少計數器。

     2.快取空物件

  也可以採用一個更為簡單粗暴的方法,如果一個查詢返回的資料為空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘。

 快取空物件會有兩個問題:

 第一,空值做了快取,意味著快取層中存了更多的鍵,需要更多的記憶體空間 ( 如果是攻擊,問題更嚴重 ),比較有效的方法是針對這類資料設定一個較短的過期時間,讓其自動剔除。

 第二,快取層和儲存層的資料會有一段時間視窗的不一致,可能會對業務有一定影響。例如過期時間設定為 5分鐘,如果此時儲存層添加了這個資料,那此段時間就會出現快取層和儲存層資料的不一致,此時可以利用訊息系統或者其他方式清除掉快取層中的空物件。

.快取雪崩

    如果快取集中在一段時間內失效,發生大量的快取穿透,所有的查詢都落在資料庫上,造成了快取雪崩。

    這個沒有完美解決辦法,但可以分析使用者行為,儘量讓失效時間點均勻分佈。大多數系統設計者考慮用加鎖或者佇列的方式保證快取的單執行緒(程序)寫,從而避免失效時大量的併發請求落到底層儲存系統上。

    解決方法

   1.加鎖排隊

 在快取失效後,通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。

 業界比較常用的做法,是使用mutex。簡單地來說,就是在快取失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用快取工具的某些帶成功操作返回值的操作(比如RedisSETNX或者MemcacheADD)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設快取;否則,就重試整個get快取的方法。

SETNX,是「SET if Not eXists」的縮寫,也就是隻有不存在的時候才設定,可以利用它來實現鎖的效果。

public String get(key) {  

      String value = redis.get(key);  

      if (value == null) { //代表快取值過期

          //設定3min的超時,防止del操作失敗的時候,下次快取過期一直不能load db  

          if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表設定成功

               value = db.get(key);  

                      redis.set(key, value, expire_secs);  

                      redis.del(key_mutex);  

              } else {  //這個時候代表同時候的其他執行緒已經load db並回設到快取了,這時候重試獲取快取值即可

                      sleep(50);  

                      get(key);  //重試

              }  

          } else {  

              return value;        

          }  

 }  

     2.資料預熱

  可以通過快取reload機制,預先去更新快取,再即將發生大併發訪問前手動觸發載入快取不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻

     3.做二級快取,或者雙快取策略。

     A1為原始快取,A2為拷貝快取,A1失效時,可以訪問A2A1快取失效時間設定為短期,A2設定為長期。

      4.快取永遠不過期

 這裡的永遠不過期包含兩層意思:

    (1) 快取上看,確實沒有設定過期時間,這就保證了,不會出現熱點key過期問題,也就是物理不過期。

     (2) 從功能上看,如果不過期,那不就成靜態的了嗎?所以我們把過期時間存在key對應的value裡,如果發現要過期了,通過一個後臺的非同步執行緒進行快取的構建,也就是邏輯過期.

 從實戰看,這種方法對於效能非常友好,唯一不足的就是構建快取時候,其餘執行緒(非構建快取的執行緒)可能訪問的是老資料,但是對於一般的網際網路功能來說這個還是可以忍受。