天天看點

緩存穿透、緩存擊穿、緩存雪崩

前言

使用緩存時,我們的業務系統大概的調用流程如下圖:

緩存穿透、緩存擊穿、緩存雪崩

考慮緩存系統,必定考慮以下三個問題:緩存穿透、緩存擊穿與失效時的雪崩效應。

一、緩存穿透

查詢一個一定不存在的資料,當我們查詢一條資料時,先去查詢緩存,如果緩存有就直接傳回,如果沒有就去查詢資料庫,然後傳回。這種情況下就可能會出現一些現象。

換句話說,緩存和資料庫都查不到該資料,是以每次請求都會打到資料庫上。

如果有黑客拿一個不存在的id去海量查詢資料庫,資料庫會因為巨大的壓力而當機。

之是以會發生穿透,就是因為緩存中沒有存儲這些空資料的key。進而導緻每次查詢都到資料庫去了。

那麼我們就可以為這些key對應的值設定為null 丢到緩存裡面去。後面再出現查詢這個key 的請求的時候,直接傳回null 。

這樣,就不用在到資料庫中去走一圈了,但是别忘了設定過期時間。它的過期時間會很短,最長不超過五分鐘。

BloomFilter 類似于一個hbase set 用來判斷某個元素(key)是否存在于某個集合中。

這種方式在大資料場景應用比較多,比如 Hbase 中使用它去判斷資料是否在磁盤上。還有在爬蟲場景判斷url 是否已經被爬取過。

這種方案可以加在第一種方案中,在緩存之前在加一層 BloomFilter ,在查詢的時候先去 BloomFilter 去查詢 key 是否存在,如果不存在就直接傳回,存在再走查緩存 -> 查 DB。

将所有可能存在的資料哈希到一個足夠大的bitmap中,一個一定不存在的資料會被 這個bitmap攔截掉,進而避免了對底層存儲系統的查詢壓力。

流程圖如下:

緩存穿透、緩存擊穿、緩存雪崩

針對于一些惡意攻擊,攻擊帶過來的大量key 是不存在的,那麼我們采用第一種方案就會緩存大量不存在key的資料。

此時我們采用第一種方案就不合适了,我們完全可以先對使用第二種方案進行過濾掉這些key。

針對這種key異常多、請求重複率比較低的資料,我們就沒有必要進行緩存,使用第二種方案直接過濾掉。

而對于空資料的key有限的,重複率比較高的,我們則可以采用第一種方式進行緩存。

二、緩存擊穿

對于一些設定了過期時間的key,如果這些key可能會在某些時間點被超高并發地通路,是一種非常“熱點”的資料。這個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的差別在于這裡針對某一key緩存,前者則是很多key。

緩存在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的并發請求過來,這些請求發現緩存過期一般都會從後端DB加載資料并回設到緩存,這個時候大并發的請求可能會瞬間把後端DB壓垮。

在平常高并發的系統中,大量的請求同時查詢一個 key 時,此時這個key正好失效了,就會導緻大量的請求都打到資料庫上面去。這種現象我們稱為緩存擊穿。

上面的現象是多個線程同時去查詢資料庫的這條資料,那麼我們可以在第一個查詢資料的請求上使用一個 互斥鎖來鎖住它。

其他的線程走到這一步拿不到鎖就等着,等第一個線程查詢到了資料,然後做緩存。後面的線程進來發現已經有緩存了,就直接走緩存。

三、緩存雪崩

緩存雪崩是指在我們設定緩存時采用了相同的過期時間,導緻緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。

緩存雪崩的情況是說,當某一時刻發生大規模的緩存失效的情況,比如你的緩存服務當機了,會有大量的請求進來直接打到DB上面。結果就是DB 稱不住,挂掉。

緩存失效時的雪崩效應對底層系統的沖擊非常可怕。大多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線 程(程序)寫,進而避免失效時大量的并發請求落到底層存儲系統上。這裡分享一個簡單方案就時講緩存失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個随機值,比如1-5分鐘随機,這樣每一個緩存的過期時間的重複率就會降低,就很難引發集體失效的事件。

緩存穿透、緩存擊穿、緩存雪崩

繼續閱讀