1. 程式人生 > 其它 >什麼是快取雪崩、擊穿、穿透?

什麼是快取雪崩、擊穿、穿透?

一、什麼是快取雪崩、擊穿、穿透?
二、快取雪崩
   2.1 大量資料同時過期
     2.1.1 均勻設定過期時間
     2.1.2 互斥鎖
     2.1.3 雙 key 策略
     2.1.4 後臺更新快取
   2.2 Redis 故障宕機
     2.2.1 服務熔斷或請求限流機制
     2.2.2 構建 Redis 快取高可靠叢集
三、快取擊穿
四、快取穿透
   4.1 非法請求的限制
   4.2 快取空值或者預設值
   4.3 使用布隆過濾器快速判斷資料是否存在
五、總結

一、什麼是快取雪崩、擊穿、穿透?

使用者的資料一般都是儲存於資料庫,資料庫的資料是落在磁碟上的,磁碟的讀寫速度可以說是計算機裡最慢的硬體了。

當用戶的請求,都訪問資料庫的話,請求數量一上來,資料庫很容易就奔潰的了,所以為了避免使用者直接訪問資料庫,會用 Redis 作為快取層。

因為 Redis 是記憶體資料庫,我們可以將資料庫的資料快取在 Redis 裡,相當於資料快取在記憶體,記憶體的讀寫速度比硬碟快好幾個數量級,這樣大大提高了系統性能。

引入了快取層,就會有快取異常的三個問題,分別是快取雪崩、快取擊穿、快取穿透

這三個問題也是面試中很常考察的問題,我們不光要清楚地知道它們是怎麼發生,還需要知道如何解決它們。

話不多說,發車!


二、快取雪崩

通常我們為了保證快取中的資料與資料庫中的資料一致性,會給 Redis 裡的資料設定過期時間,當快取資料過期後,使用者訪問的資料如果不在快取裡,業務系統需要重新生成快取,因此就會訪問資料庫,並將資料更新到 Redis 裡,這樣後續請求都可以直接命中快取。

那麼,當大量快取資料在同一時間過期(失效)或者 Redis 故障宕機時,如果此時有大量的使用者請求,都無法在 Redis 中處理,於是全部請求都直接訪問資料庫,從而導致資料庫的壓力驟增,嚴重的會造成資料庫宕機,從而形成一系列連鎖反應,造成整個系統崩潰,這就是快取雪崩的問題。

可以看到,發生快取雪崩有兩個原因:

2.1 大量資料同時過期

Redis 故障宕機;
不同的誘因,應對的策略也會不同。

大量資料同時過期
針對大量資料同時過期而引發的快取雪崩問題,常見的應對方法有下面這幾種:

均勻設定過期時間;
互斥鎖;
雙 key 策略;
後臺更新快取;

2.1.1 均勻設定過期時間

如果要給快取資料設定過期時間,應該避免將大量的資料設定成同一個過期時間。我們可以在對快取資料設定過期時間時,給這些資料的過期時間加上一個隨機數

,這樣就保證資料不會在同一時間過期。

2.1.2 互斥鎖

當業務執行緒在處理使用者請求時,如果發現訪問的資料不在 Redis 裡,就加個互斥鎖,保證同一時間內只有一個請求來構建快取(從資料庫讀取資料,再將資料更新到 Redis 裡),當快取構建完成後,再釋放鎖。未能獲取互斥鎖的請求,要麼等待鎖釋放後重新讀取快取,要麼就返回空值或者預設值。

實現互斥鎖的時候,最好設定超時時間,不然第一個請求拿到了鎖,然後這個請求發生了某種意外而一直阻塞,一直不釋放鎖,這時其他請求也一直拿不到鎖,整個系統就會出現無響應的現象。

2.1.3 雙 key 策略

我們對快取資料可以使用兩個 key,一個是主 key,會設定過期時間,一個是備 key,不會設定過期,它們只是 key 不一樣,但是 value 值是一樣的,相當於給快取資料做了個副本。

當業務執行緒訪問不到「主 key 」的快取資料時,就直接返回「備 key 」的快取資料,然後在更新快取的時候,同時更新「主 key 」和「備 key 」的資料

2.1.4 後臺更新快取

業務執行緒不再負責更新快取,快取也不設定有效期,而是讓快取“永久有效”,並將更新快取的工作交由後臺執行緒定時更新。

事實上,快取資料不設定有效期,並不是意味著資料一直能在記憶體裡,因為當系統記憶體緊張的時候,有些快取資料會被“淘汰”,而在快取被“淘汰”到下一次後臺定時更新快取的這段時間內,業務執行緒讀取快取失敗就返回空值,業務的視角就以為是資料丟失了。

解決上面的問題的方式有兩種。

第一種方式:後臺執行緒不僅負責定時更新快取,而且也負責頻繁地檢測快取是否有效,檢測到快取失效了,原因可能是系統緊張而被淘汰的,於是就要馬上從資料庫讀取資料,並更新到快取。

這種方式的檢測時間間隔不能太長,太長也導致使用者獲取的資料是一個空值而不是真正的資料,所以檢測的間隔最好是毫秒級的,但是總歸是有個間隔時間,使用者體驗一般。

第二種方式:在業務執行緒發現快取資料失效後(快取資料被淘汰),通過訊息佇列傳送一條訊息通知後臺執行緒更新快取,後臺執行緒收到訊息後,在更新快取前可以判斷快取是否存在,存在就不執行更新快取操作;不存在就讀取資料庫資料,並將資料載入到快取。這種方式相比第一種方式快取的更新會更及時,使用者體驗也比較好。

在業務剛上線的時候,我們最好提前把資料緩起來,而不是等待使用者訪問才來觸發快取構建,這就是所謂的快取預熱,後臺更新快取的機制剛好也適合幹這個事情。

2.2 Redis 故障宕機

針對 Redis 故障宕機而引發的快取雪崩問題,常見的應對方法有下面這幾種:

服務熔斷或請求限流機制;
構建 Redis 快取高可靠叢集;

2.2.1 服務熔斷或請求限流機制

因為 Redis 故障宕機而導致快取雪崩問題時,我們可以啟動服務熔斷機制,暫停業務應用對快取服務的訪問,直接返回錯誤,不用再繼續訪問資料庫,從而降低對資料庫的訪問壓力,保證資料庫系統的正常執行,然後等到 Redis 恢復正常後,再允許業務應用訪問快取服務。

服務熔斷機制是保護資料庫的正常允許,但是暫停了業務應用訪問快取服系統,全部業務都無法正常工作

為了減少對業務的影響,我們可以啟用請求限流機制,只將少部分請求傳送到資料庫進行處理,再多的請求就在入口直接拒絕服務,等到 Redis 恢復正常並把快取預熱完後,再解除請求限流的機制。

2.2.2 構建 Redis 快取高可靠叢集

服務熔斷或請求限流機制是快取雪崩發生後的應對方案,我們最好通過主從節點的方式構建 Redis 快取高可靠叢集。

如果 Redis 快取的主節點故障宕機,從節點可以切換成為主節點,繼續提供快取服務,避免了由於 Redis 故障宕機而導致的快取雪崩問題。


三、快取擊穿

我們的業務通常會有幾個資料會被頻繁地訪問,比如秒殺活動,這類被頻地訪問的資料被稱為熱點資料。

如果快取中的某個熱點資料過期了,此時大量的請求訪問了該熱點資料,就無法從快取中讀取,直接訪問資料庫,資料庫很容易就被高併發的請求沖垮,這就是快取擊穿的問題。

可以發現快取擊穿跟快取雪崩很相似,你可以認為快取擊穿是快取雪崩的一個子集。

應對快取擊穿可以採取前面說到兩種方案:

互斥鎖方案,保證同一時間只有一個業務執行緒更新快取,未能獲取互斥鎖的請求,要麼等待鎖釋放後重新讀取快取,要麼就返回空值或者預設值。
不給熱點資料設定過期時間,由後臺非同步更新快取,或者在熱點資料準備要過期前,提前通知後臺執行緒更新快取以及重新設定過期時間;


四、快取穿透

當發生快取雪崩或擊穿時,資料庫中還是儲存了應用要訪問的資料,一旦快取恢復相對應的資料,就可以減輕資料庫的壓力,而快取穿透就不一樣了。

當用戶訪問的資料,既不在快取中,也不在資料庫中,導致請求在訪問快取時,發現快取缺失,再去訪問資料庫時,發現數據庫中也沒有要訪問的資料,沒辦法構建快取資料,來服務後續的請求。那麼當有大量這樣的請求到來時,資料庫的壓力驟增,這就是快取穿透的問題。

快取穿透的發生一般有這兩種情況:

業務誤操作,快取中的資料和資料庫中的資料都被誤刪除了,所以導致快取和資料庫中都沒有資料;
黑客惡意攻擊,故意大量訪問某些讀取不存在資料的業務;
應對快取穿透的方案,常見的方案有三種。

第一種方案,非法請求的限制;
第二種方案,快取空值或者預設值;
第三種方案,使用布隆過濾器快速判斷資料是否存在,避免通過查詢資料庫來判斷資料是否存在;

4.1 非法請求的限制

當有大量惡意請求訪問不存在的資料的時候,也會發生快取穿透,因此在 API 入口處我們要判斷求請求引數是否合理,請求引數是否含有非法值、請求欄位是否存在,如果判斷出是惡意請求就直接返回錯誤,避免進一步訪問快取和資料庫。

4.2 快取空值或者預設值

當我們線上業務發現快取穿透的現象時,可以針對查詢的資料,在快取中設定一個空值或者預設值,這樣後續請求就可以從快取中讀取到空值或者預設值,返回給應用,而不會繼續查詢資料庫。

4.3 使用布隆過濾器快速判斷資料是否存在,避免通過查詢資料庫來判斷資料是否存在。

我們可以在寫入資料庫資料時,使用布隆過濾器做個標記,然後在使用者請求到來時,業務執行緒確認快取失效後,可以通過查詢布隆過濾器快速判斷資料是否存在,如果不存在,就不用通過查詢資料庫來判斷資料是否存在。

即使發生了快取穿透,大量請求只會查詢 Redis 和布隆過濾器,而不會查詢資料庫,保證了資料庫能正常執行,Redis 自身也是支援布隆過濾器的。

那問題來了,布隆過濾器是如何工作的呢?接下來,我介紹下。

布隆過濾器由「初始值都為 0 的點陣圖陣列」和「 N 個雜湊函式」兩部分組成。當我們在寫入資料庫資料時,在布隆過濾器裡做個標記,這樣下次查詢資料是否在資料庫時,只需要查詢布隆過濾器,如果查詢到資料沒有被標記,說明不在資料庫中。

布隆過濾器會通過 3 個操作完成標記:

第一步,使用 N 個雜湊函式分別對資料做雜湊計算,得到 N 個雜湊值;
第二步,將第一步得到的 N 個雜湊值對點陣圖陣列的長度取模,得到每個雜湊值在點陣圖陣列的對應位置。
第三步,將每個雜湊值在點陣圖陣列的對應位置的值設定為 1;
舉個例子,假設有一個位圖陣列長度為 8,雜湊函式 3 個的布隆過濾器。

在資料庫寫入資料 x 後,把資料 x 標記在布隆過濾器時,資料 x 會被 3 個雜湊函式分別計算出 3 個雜湊值,然後在對這 3 個雜湊值對 8 取模,假設取模的結果為 1、4、6,然後把點陣圖陣列的第 1、4、6 位置的值設定為 1。當應用要查詢資料 x 是否資料庫時,通過布隆過濾器只要查到點陣圖陣列的第 1、4、6 位置的值是否全為 1,只要有一個為 0,就認為資料 x 不在資料庫中。

布隆過濾器由於是基於雜湊函式實現查詢的,高效查詢的同時存在雜湊衝突的可能性,比如資料 x 和資料 y 可能都落在第 1、4、6 位置,而事實上,可能資料庫中並不存在資料 y,存在誤判的情況。

所以,查詢布隆過濾器說資料存在,並不一定證明資料庫中存在這個資料,但是查詢到資料不存在,資料庫中一定就不存在這個資料。


五、總結

快取異常會面臨的三個問題:快取雪崩、擊穿和穿透。

其中,快取雪崩和快取擊穿主要原因是資料不在快取中,而導致大量請求訪問了資料庫,資料庫壓力驟增,容易引發一系列連鎖反應,導致系統奔潰。不過,一旦資料被重新載入回快取,應用又可以從快取快速讀取資料,不再繼續訪問資料庫,資料庫的壓力也會瞬間降下來。因此,快取雪崩和快取擊穿應對的方案比較類似。

而快取穿透主要原因是資料既不在快取也不在資料庫中。因此,快取穿透與快取雪崩、擊穿應對的方案不太一樣。

我這裡整理了表格,你可以從下面這張表格很好的知道快取雪崩、擊穿和穿透的區別以及應對方案。


參考資料:

1.《極客時間:Redis核心技術與實戰》

https://github.com/doocs/advanced-java/blob/main/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md

https://medium.com/@mena.meseha/3-major-problems-and-solutions-in-the-cache-world-155ecae41d4f


原文連結:https://blog.csdn.net/qq_34827674/article/details/123463175