緩存穿透了怎麼辦?教你機智應對

在現在互聯網架構中,幾乎每個互聯網項目都會引入緩存系統,比如 Redis、Memcached。來保護下游數據庫和提升系統併發量。不管使用哪種緩存系統都有可能遇到

緩存穿透的問題。

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

當然緩存系統是不可避免的,少量的緩存穿透對系統也沒有損害,不可避免的原因有以下幾點:

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

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

基於存在這種大量緩存穿透的可能性,所以我們就需要從根源上解決緩存穿透的問題,解決緩存穿透,目前一般有兩種方案:

緩存空值和使用隆過濾器

緩存空值

如果我們系統是遇到攻擊的話,那麼很有可能查詢的值是偽造的,必然大概率不存在我們的系統中,這樣無論查詢多少次,在緩存中一直不存在,這樣緩存穿透就一直存在。

在這種情況下,我們可以在緩存系統中緩存一個空值,防止穿透一直存在,但是因為空值並不是準確的業務數據,並且會佔用緩存的空間,所以我們會給這個空值加一個比較短的過期時間,讓空值在短時間之內能夠快速過期淘汰。下面是一段偽代碼:

<code>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);
}/<code>

雖然這種方法可以解決緩存穿透的問題,但是這種方式也存在弊端,

因為在緩存系統中存了大量的空值,浪費緩存的存儲空間,如果緩存空間被佔滿了,還會還會剔除掉一些已經被緩存的用戶信息反而會造成緩存命中率的下降。

使用布隆過濾器

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

布隆過濾器存在一定的誤判,因為採用hash算法,就一定會存在哈希衝突,這樣就可能造成不在數據庫中的元素被判斷在布隆過濾器中存在,但是不在布隆過濾器中的元素一定不存在數據庫中。

利用布隆過濾器的這個特點可以解決緩存穿透的問題,在服務啟動的時候先把數據的查詢條件,例如數據的 ID 映射到布隆過濾器上,當然如果新增數據時,除了寫入到數據庫中之外,也需要將數據的ID存入到布隆過濾器中

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


緩存穿透了怎麼辦?教你機智應對


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

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

以上就是今天分享的內容,希望對您的學習或者工作有所幫助,如果您覺得文章不錯,歡迎點個贊和轉發,謝謝。

最後

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


作者:平頭哥的技術博文
鏈接:https://juejin.im/post/5e664333e51d4527017976be


分享到:


相關文章: