這篇文章帶大家聊聊Redis中的三種常見快取異常:快取穿透、快取擊穿和快取雪崩,透過它們來聊一聊Redis中的熱點key儲存問題,希望對大家有幫助!
快取穿透、快取擊穿和快取雪崩是Redis面試當中和實際開發中,經常需要考慮的問題。很多人對該問題的產生、原因和解決方案還是不夠清晰。其實大家針對該三種情況,去仔細分析一個產生的原理就能很好的找到一個好的解決方案。 【相關推薦:Redis影片教學】
本文透過定義、案例、危害和解決方案的幾個角度,來幫助你快速了解該三個問題。
相信大家在網路上也看到很多解決這三種問題的解決方案,其中的一些方案是否是一個正確的
方案呢?本文也將一一分析此類方案的優缺點。
下圖為本文的內容大綱,文章也是圍繞這幾點進行分析與總結。
#快取穿透、快取擊穿和快取雪崩都是因為快取中資料不存在,導致走資料庫去查詢資料。
由於快取資料不存在,所有的請求都會走到資料庫,因此會導致資料庫的壓力過大甚至出現服務崩潰,導致整個系統無法使用。
#定義:快取穿透是由於客戶端求的資料在快取中不存在,然後去查詢資料庫,然而資料庫沒有客戶端要查詢的數據,導致每一次請求都會走資料庫查詢操作。 真正的問題在於該資料本身就是不存在的
。
範例:客戶端要求商品詳情資訊時,攜帶一個商品ID,此時該商品ID是不存在的(不管是快取中還是資料庫中)。導致每一次要求該ID商品的資料資訊都會走資料庫。
危害:由於請求的參數對應的資料根本不存在,會導致每一次都會請求資料庫,增加資料庫的壓力或服務崩潰,更有甚至影響到其他的業務模組。經常發生在使用者惡意請求
的情況下會發生。
解決方案:
1、根據請求的參數快取一個null值。並且為該值設定一個過期時間,可以將時間設定短暫一點。
2、使用布隆過濾器,先透過布隆過濾器進行篩選,如果在過濾器中存在則去查詢資料庫,然後再加入到快取中。如果不存在則直接傳回客戶端資料不存在。
3、由於快取穿透可能是使用者發起惡意請求,可以將使用者ip給記錄下來,針對惡意的ip請求進行封鎖。
方案分析:
第一個方案,針對不存在的key,會快取一個空的值。假設這樣的請求特別多,是否都會一一去設定一個空值的緩存,此時Redis中就存在大量無效的緩存空值。假設這樣的key是商品或文章類別的ID,我們在設定空值之後,如果後台加入資料應該去更新ID對應的快取值,並設定一個合理的過期時間。
第二種方案,也是業界使用最多的方案。布隆過濾器的優點在於基於Redis實現,記憶體操作且底層的實作也是非常節約記憶體。當後台新增資料成功時,將該資料的ID加入到布隆過濾器中,前端在請求時先走布隆過濾器進行驗證是否存在。但布隆過濾器也存在一個弊端,就是hash衝突問題。這裡的hash衝突是什麼意思呢?是說多個ID在進行hash計算時,得到的hash位元都是同一個值,這就導致在驗證是否存在時誤判。本身是有的,得到的結果是沒有。 布隆過濾器的一個弊端就是,它說有不一定有,它說沒有就一點是沒有的。
第三種方案,針對同一用戶一段時間內發起大量的請求,觸發快取穿透機制,此時我們可以顯示該客戶端的存取。但攻擊者如果是發動DDOS這樣的攻擊,是沒辦法完全的避免此類攻擊,因此這種方案不是一個很好的解決方案。
方案總結:
#我們先在請求層級增加第3中方案,做一個限流機制、IP黑名單機制,控制一些惡意的請求,如果是誤判我們可以實現IP解封這樣的操作。在快取層則使用第1中方案實作。設定一個合理的快取時間。
對於能容忍誤判的業務場景,可以直接才用第2方案實現。完全基於Redis,減少了系統的複雜度。
#定義:快取擊穿是因為某個熱點key不存在,導致走資料庫查詢。增加了資料庫的壓力。這種壓力可能是瞬間的,也可能是比較持久的。 真正的問題在於該key是存在,只是快取中不存在,導致走資料庫操作
。
範例:有一個熱門的商品,用戶查看商品詳情時攜帶商品的ID以獲取到商品的詳情資訊。此時快取中的資料已經過期了,因此來的所有請求都要走資料庫去查詢。
危害:相對快取穿透而言,該資料在資料庫中是存在的,只是因為快取過期了,導致要走一次資料庫,然後在加入到快取中,下次請求就能正常走快取.所謂的危害同樣的還是針對資料庫層面的危害。
解決方案:
1、加互斥鎖。針對第一個請求,發現快取中沒有數據,此時查詢資料庫加入到快取裡面。這樣後面的請求就不需要走資料庫查詢。
2、增加業務邏輯過期時間。在設定快取時,我們可以新增一個快取過期時間。每次去讀取的時候,做一個判斷,如果這個過期時間與當前時間小於一個範圍,觸發一個後台線程,去數據庫拉取一下數據,接著更新一下緩存數據和緩存的過期時間。其實原理就是程式碼層面給快取延長快取時長。
3、資料預熱。實作透過後台把資料加到快取裡面。例如秒殺場景開始前,就把商品的庫存加到快取裡面,這樣使用者請求來了之後,就直接走快取。
4、永久不過期。在給快取設定過期時間時,讓它永久不過期。後台單獨開啟一個線程,來維護這些快取的過期時間和資料更新。
方案分析:
互斥鎖保證了只有一個請求走資料庫,這是一個優點。但是對於分散式的系統,得才用分散式鎖實現,分散式鎖的實作本身就有一定的困難,這樣提升了系統的複雜度。
第2種方案,利用Redis不過期,業務過期的方案實現。保證了每一次請求都能拿到數據,同時也可以做到一個後台執行緒去更新數據。缺點在於後台執行緒沒有更新完數據,此時請求拿到的數據是舊數據,可能對應即時性要求高的業務場景有弊端。
第3種方案,使用快取預熱每次載入都走緩存,與第2種方案差不多。不過也存在熱點資料更新問題,因此此方案適合資料即時性要求不高的資料。
第4中方案,和第2、3種方案類似,在此基礎上進行了一定優化,使用後台非同步執行緒主動去更新快取資料。難點在於更新的頻率控制。
方案總結:
#對於即時性要求高的數據,推薦使用第1種方案,雖然在技術上有一定的難度但是能做到數據的即時性處理。如果發生某些請求等待時間久,可以傳回異常,讓客戶端重新發送一次請求。
對於即時性要求不高的數據,可以使用第4種方案。
# 定義:前面說到快取擊穿,是因為快取中的某個熱點key失效,導致大量請求走資料庫。然而緩存雪崩其實也是同樣的道理,只不過這個更嚴重而已,是大部分緩存的key失效,而不是一個或者兩個key失效。
範例:在一個電商系統中,某一個分類下的商品資料在快取中都失效了。然而當前系統的許多請求都是該分類下面的商品資料。這樣就導致所有的請求都走資料庫查詢。
危害:由於一瞬間大量的請求湧入,每個請求都要走資料庫進行查詢。資料庫瞬間流量湧入,嚴重增加資料庫負擔,容易導致資料庫直接癱瘓。
解決方案:
1、快取時間隨機。因為某一時間,大量的快取失效,說明快取的過期時間比較集中。我們直接將過期的時間設定為不集中,隨機打亂。這樣快取過期時間相對不會很集中,就不會出現同一時刻大量請求走資料庫進行查詢操作。
2、多層快取。不單純的靠Redis來做緩存,我們也可以使用memcached來做緩存(這裡只是舉一個例子,其他的快取服務也可以)。快取資料時,對Redis做一個緩存,對memcached做一個快取。如果Redis失效了,我們可以走memcached。
3、互斥鎖。快取擊穿中我們提到了使用互斥鎖來實現,同樣我們也可以用在雪崩的情況下。
4、設定過期標誌。其實也可以用到快取擊穿中講到的永久不過期。當請求時,判斷過期時間,如果臨近過期時間則設定過期標誌,觸發一個獨立的執行緒去對這個快取進行更新。
方案分析:
第1種方案採用隨機數快取時間,能確保key的失效時間分散。困難在於如何設定快取時間,如果對於一些需要設定短快取時間並且資料量非常大的數據,則該方案就需要合理的控制時間。
第2種方案使用多層緩存,可以保證請求全部走緩存資料。但這樣增加了系統的架構難度,以及其他的各種問題,例如快取多層次更新。
第3種方案使用互斥鎖,在快取擊穿中我們提到了互斥鎖,在雪崩的場景中我們雖然能使用,但是這樣會產生大量的分佈式鎖。
第4種方案使用邏輯快取時間,很好的保證了系統的快取壓力。
方案總結:
在實際的專案中推薦使用第1、2和4種方案試下會更好一些。
快取穿透是因為資料庫本身沒有該資料。
快取擊穿和快取雪崩是資料庫中存在該數據,只是快取中的資料失效了,導致重新要查詢一次資料庫再加入到快取中去。
快取擊穿是針對部分熱點key,而快取雪崩是大面積快取失效。兩則原理上其實是一樣的,無非就是針對快取的key的劃分不同而已。
更多程式設計相關知識,請造訪:程式設計入門! !
以上是分析Redis中熱點key儲存問題,聊聊快取異常的解決方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!