緩存穿透了怎麼辦?

在如今互聯網架構中,幾乎每一個互聯網項目都會引入緩存系統,好比 Redis、Memcached。來保護下游數據庫和提高系統併發量。無論使用哪一種緩存系統都有可能遇到緩存穿透的問題。java

緩存穿透是指在緩存系統中沒有查詢到數據,而不得不將請求打到數據庫上查詢的狀況。算法

固然緩存系統是不可避免的,少許的緩存穿透對系統也沒有損害,不可避免的緣由有如下幾點:數據庫

  • 緩存系統的容量是有限的,不可能存儲系統全部的數據,那麼在查詢未緩存數據的時候就會發生緩存穿透。
  • 另外一方面就是基於‘二八原則’,咱們一般只會緩存經常使用的那 20% 的熱點數據。

正常狀況下的緩存穿透是沒什麼傷害的,可是若是你的系統遭遇攻擊,存在大量的緩存穿透的話,那麼可能就是一個麻煩了,若是大量的緩存穿透超過了後端服務器的承受能力,那麼就有可能形成服務崩潰,這是不可接受的。後端

基於存在這種大量緩存穿透的可能性,因此咱們就須要從根源上解決緩存穿透的問題,解決緩存穿透,目前通常有兩種方案:緩存空值和使用布隆過濾器數組

緩存空值

若是咱們系統是遇到攻擊的話,那麼頗有可能查詢的值是僞造的,必然大機率不存在咱們的系統中,這樣不管查詢多少次,在緩存中一直不存在,這樣緩存穿透就一直存在。緩存

在這種狀況下,咱們能夠在緩存系統中緩存一個空值,防止穿透一直存在,可是由於空值並非準確的業務數據,而且會佔用緩存的空間,因此咱們會給這個空值加一個比較短的過時時間,讓空值在短期以內可以快速過時淘汰。下面是一段僞代碼:服務器

Object nullValue = new Object();
try {
  Object valueFromDB = getFromDB(uid); //從數據庫中查詢數據
  if (valueFromDB == null) {
    cache.set(uid, nullValue, 10);   //若是從數據庫中查詢到空值,就把空值寫入緩存,設置較短的超時時間
  } else {
    cache.set(uid, valueFromDB, 1000);
  }
} catch(Exception e) {
  cache.set(uid, nullValue, 10);
}
複製代碼

雖然這種方法能夠解決緩存穿透的問題,可是這種方式也存在弊端,由於在緩存系統中存了大量的空值,浪費緩存的存儲空間,若是緩存空間被佔滿了,還會還會剔除掉一些已經被緩存的用戶信息反而會形成緩存命中率的降低。架構

使用布隆過濾器

1970年布隆提出了一種布隆過濾器的算法,用來判斷一個元素是否在一個集合中。布隆過濾器底層是一個超級大的 bit 數組,默認值都是 0 ,一個元素經過多個hash函數映射到這個 bit 數組上,而且將 0 改爲 1。固然布隆過濾器也不須要咱們實現,在 Google 的 guava 包中有提供布隆過濾器,有興趣的小夥伴能夠研究研究。併發

布隆過濾器存在必定的誤判,由於採用hash算法,就必定會存在哈希衝突,這樣就可能形成不在數據庫中的元素被判斷在布隆過濾器中存在,可是不在布隆過濾器中的元素必定不存在數據庫中。函數

利用布隆過濾器的這個特色能夠解決緩存穿透的問題,在服務啓動的時候先把數據的查詢條件,例如數據的 ID 映射到布隆過濾器上,固然若是新增數據時,除了寫入到數據庫中以外,也須要將數據的ID存入到布隆過濾器中

咱們在查詢某條數據時,先判斷這個查詢的 ID 是否存在布隆過濾器中,若是不存在就直接返回空值,而不須要繼續查詢數據庫和緩存,存在布隆過濾器中才繼續查詢數據庫和緩存,這樣就解決緩存穿透的問題。

使用布隆過濾器示意圖

固然布隆過濾器有缺陷,除了上面咱們講到過的存在必定的誤判,還有一個就是不支持刪除

緩存空值和使用布隆過濾器均可以在必定程度上解決緩存穿透的問題,各自有各自的優點,具體如何使用根據特定的場景舍取。

以上就是今天分享的內容,但願對您的學習或者工做有所幫助,若是您以爲文章不錯,歡迎點個贊和轉發,謝謝。

最後

目前互聯網上不少大佬都有緩存穿透相關文章,若有雷同,請多多包涵了。原創不易,碼字不易,還但願你們多多支持。若文中有所錯誤之處,還望提出,謝謝。

互聯網平頭哥
相關文章
相關標籤/搜索