緩存雪崩、擊穿、穿透一旦發(fā)生,會導(dǎo)致大量的請求積壓到數(shù)據(jù)庫層。如果請求的并發(fā)量很大,就會導(dǎo)致數(shù)據(jù)庫宕機(jī)或是故障,這就是很嚴(yán)重的生產(chǎn)事故了。
俗話說,知己知彼,百戰(zhàn)不殆。了解了問題的成因,我們就能夠在應(yīng)用Redis緩存時,進(jìn)行合理的緩存設(shè)置,以及相應(yīng)的業(yè)務(wù)應(yīng)用前端設(shè)置,提前做好準(zhǔn)備。
先來看下緩存雪崩
緩存雪崩
大量的應(yīng)用請求無法在Redis緩存中進(jìn)行處理,緊接著,應(yīng)用將大量請求發(fā)送到數(shù)據(jù)庫層,也會給后端系統(tǒng)(比如DB)帶來很大壓力,導(dǎo)致數(shù)據(jù)庫層的壓力激增,造成數(shù)據(jù)庫后端故障,從而引起應(yīng)用服務(wù)器雪崩。
緩存雪崩一般是由兩個原因?qū)е碌?,?yīng)對方案也有所不同,我們一個個來看。
第一個原因是:緩存中有大量數(shù)據(jù)同時過期,導(dǎo)致大量請求無法得到處理。
具體來說,當(dāng)數(shù)據(jù)保存在緩存中,并且設(shè)置了過期時間時,如果在某一個時刻,大量數(shù)據(jù)同時過期,此時,應(yīng)用再訪問這些數(shù)據(jù)的話,就會發(fā)生緩存缺失。
緊接著,應(yīng)用就會把請求發(fā)送給數(shù)據(jù)庫,從數(shù)據(jù)庫中讀取數(shù)據(jù)。如果應(yīng)用的并發(fā)請求量很大,那么數(shù)據(jù)庫的壓力也就很大,這會進(jìn)一步影響到數(shù)據(jù)庫的其他正常業(yè)務(wù)請求處理。
我們來看一個簡單的例子
應(yīng)用程序發(fā)送讀請求到 緩存 Redis 中,此時命中緩存,也就是緩存中有數(shù)據(jù),這樣是很 OK的,沒有問題
但是如果大量的緩存數(shù)據(jù)同時過期呢,就會導(dǎo)致大量的并發(fā)請求到了數(shù)據(jù)庫 MySQL 中,所以就會導(dǎo)致數(shù)據(jù)的崩潰,就是剛才說的情況
針對大量數(shù)據(jù)同時失效帶來的緩存雪崩問題,老師給你提供兩種解決方案。
首先,我們可以避免給大量的數(shù)據(jù)設(shè)置相同的過期時間。如果業(yè)務(wù)層的確要求有些數(shù)據(jù)同時失效,你可以在用EXPIRE命令給每個數(shù)據(jù)設(shè)置過期時間時,給這些數(shù)據(jù)的過期時間增加一個較小的隨機(jī)數(shù)(例如,隨機(jī)增加1~3分鐘),這樣一來,不同數(shù)據(jù)的過期時間有所差別,但差別又不會太大,既避免了大量數(shù)據(jù)同時過期,同時也保證了這些數(shù)據(jù)基本在相近的時間失效,仍然能滿足業(yè)務(wù)需求。
除了微調(diào)過期時間,我們還可以通過服務(wù)降級,來應(yīng)對緩存雪崩。
所謂的服務(wù)降級,是指發(fā)生緩存雪崩時,針對不同的數(shù)據(jù)采取不同的處理方式。
- 當(dāng)業(yè)務(wù)應(yīng)用訪問的是非核心數(shù)據(jù)(例如電商商品屬性)時,暫時停止從緩存中查詢這些數(shù)據(jù),而是直接返回預(yù)定義信息、空值或是錯誤信息;
- 當(dāng)業(yè)務(wù)應(yīng)用訪問的是核心數(shù)據(jù)(例如電商商品庫存)時,仍然允許查詢緩存,如果緩存缺失,也可以繼續(xù)通過數(shù)據(jù)庫讀取。
這樣一來,只有部分過期數(shù)據(jù)的請求會發(fā)送到數(shù)據(jù)庫,數(shù)據(jù)庫的壓力就沒有那么大了。
除了大量數(shù)據(jù)同時失效會導(dǎo)致緩存雪崩,還有一種情況也會發(fā)生緩存雪崩,那就是,Redis緩存實例發(fā)生故障宕機(jī)了,無法處理請求,這就會導(dǎo)致大量請求一下子積壓到數(shù)據(jù)庫層,從而發(fā)生緩存雪崩。
一般來說,一個Redis實例可以支持?jǐn)?shù)萬級別的請求處理吞吐量,而單個數(shù)據(jù)庫可能只能支持?jǐn)?shù)千級別的請求處理吞吐量,它們兩個的處理能力可能相差了近十倍。由于緩存雪崩,Redis緩存失效,所以,數(shù)據(jù)庫就可能要承受近十倍的請求壓力,從而因為壓力過大而崩潰。
此時,因為Redis實例發(fā)生了宕機(jī),我們需要通過其他方法來應(yīng)對緩存雪崩了。
你可以在業(yè)務(wù)系統(tǒng)中實現(xiàn)服務(wù)熔斷或請求限流機(jī)制。
所謂的服務(wù)熔斷,是指在發(fā)生緩存雪崩時,為了防止引發(fā)連鎖的數(shù)據(jù)庫雪崩,甚至是整個系統(tǒng)的崩潰,我們暫停業(yè)務(wù)應(yīng)用對緩存系統(tǒng)的接口訪問。再具體點說,就是業(yè)務(wù)應(yīng)用調(diào)用緩存接口時,緩存客戶端并不把請求發(fā)給Redis緩存實例,而是直接返回,等到Redis緩存實例重新恢復(fù)服務(wù)后,再允許應(yīng)用請求發(fā)送到緩存系統(tǒng)。
這樣一來,我們就避免了大量請求因緩存缺失,而積壓到數(shù)據(jù)庫系統(tǒng),保證了數(shù)據(jù)庫系統(tǒng)的正常運行。
在業(yè)務(wù)系統(tǒng)運行時,我們可以監(jiān)測Redis緩存所在機(jī)器和數(shù)據(jù)庫所在機(jī)器的負(fù)載指標(biāo),例如每秒請求數(shù)、CPU利用率、內(nèi)存利用率等。如果我們發(fā)現(xiàn)Redis緩存實例宕機(jī)了,而數(shù)據(jù)庫所在機(jī)器的負(fù)載壓力突然增加(例如每秒請求數(shù)激增),此時,就發(fā)生緩存雪崩了。大量請求被發(fā)送到數(shù)據(jù)庫進(jìn)行處理。我們可以啟動服務(wù)熔斷機(jī)制,暫停業(yè)務(wù)應(yīng)用對緩存服務(wù)的訪問,從而降低對數(shù)據(jù)庫的訪問壓力
服務(wù)熔斷雖然可以保證數(shù)據(jù)庫的正常運行,但是暫停了整個緩存系統(tǒng)的訪問,對業(yè)務(wù)應(yīng)用的影響范圍大。為了盡可能減少這種影響
我們也可以進(jìn)行請求限流。
這里說的請求限流,就是指,我們在業(yè)務(wù)系統(tǒng)的請求入口前端控制每秒進(jìn)入系統(tǒng)的請求數(shù),避免過多的請求被發(fā)送到數(shù)據(jù)庫。
給你舉個例子。假設(shè)業(yè)務(wù)系統(tǒng)正常運行時,請求入口前端允許每秒進(jìn)入系統(tǒng)的請求是1萬個,其中,9000個請求都能在緩存系統(tǒng)中進(jìn)行處理,只有1000個請求會被應(yīng)用發(fā)送到數(shù)據(jù)庫進(jìn)行處理。
一旦發(fā)生了緩存雪崩,數(shù)據(jù)庫的每秒請求數(shù)突然增加到每秒1萬個,此時,我們就可以啟動請求限流機(jī)制,在請求入口前端只允許每秒進(jìn)入系統(tǒng)的請求數(shù)為1000個,再多的請求就會在入口前端被直接拒絕服務(wù)。所以,使用了請求限流,就可以避免大量并發(fā)請求壓力傳遞到數(shù)據(jù)庫層。
使用服務(wù)熔斷或是請求限流機(jī)制,來應(yīng)對Redis實例宕機(jī)導(dǎo)致的緩存雪崩問題,是屬于“事后諸葛亮”,也就是已經(jīng)發(fā)生緩存雪崩了,我們使用這兩個機(jī)制,來降低雪崩對數(shù)據(jù)庫和整個業(yè)務(wù)系統(tǒng)的影響。
老師這里除了這個方法外,還有一個就是事前預(yù)防
通過主從節(jié)點的方式構(gòu)建Redis緩存高可靠集群。如果Redis緩存的主節(jié)點故障宕機(jī)了,從節(jié)點還可以切換成為主節(jié)點,繼續(xù)提供緩存服務(wù),避免了由于緩存實例宕機(jī)而導(dǎo)致的緩存雪崩問題。
高可用集群的搭建在之前的分布式中間件中已經(jīng)講過了,如果不知道的同學(xué),可以看下之前的錄播視頻還有搭建的資料
緩存雪崩是發(fā)生在大量數(shù)據(jù)同時失效的場景下
緩存擊穿
而接下來介紹的緩存擊穿,是發(fā)生在某個熱點數(shù)據(jù)失效的場景下。和緩存雪崩相比,緩存擊穿失效的數(shù)據(jù)數(shù)量要小很多,應(yīng)對方法也不一樣,我們來看下。
緩存擊穿是指,針對某個訪問非常頻繁的熱點數(shù)據(jù)的請求,無法在緩存中進(jìn)行處理,說白了就是在一般的高并發(fā)系統(tǒng)中,大量的請求同時查詢一個key,這個key剛好失效了
緊接著,訪問該數(shù)據(jù)的大量請求,一下子都發(fā)送到了后端數(shù)據(jù)庫,導(dǎo)致了數(shù)據(jù)庫壓力激增,會影響數(shù)據(jù)庫處理其他請求。
緩存擊穿的情況,經(jīng)常發(fā)生在熱點數(shù)據(jù)過期失效時
為了避免緩存擊穿給數(shù)據(jù)庫帶來的激增壓力,我們的解決方法也比較直接,對于訪問特別頻繁的熱點數(shù)據(jù),我們就不設(shè)置過期時間了。這樣一來,對熱點數(shù)據(jù)的訪問請求,都可以在緩存中進(jìn)行處理,而Redis數(shù)萬級別的高吞吐量可以很好地應(yīng)對大量的并發(fā)請求訪問。
好了,到這里,你了解了緩存雪崩和緩存擊穿問題,以及它們的應(yīng)對方案。當(dāng)發(fā)生緩存雪崩或擊穿時,數(shù)據(jù)庫中還是保存了應(yīng)用要訪問的數(shù)據(jù)。
緩存穿透
接下來,再來介紹緩存穿透問題,和雪崩、擊穿問題不一樣,緩存穿透發(fā)生時,數(shù)據(jù)也不在數(shù)據(jù)庫中,這會同時給緩存和數(shù)據(jù)庫帶來訪問壓力,那該怎么辦呢?我們來具體看下。
當(dāng)應(yīng)用程序請求要訪問的數(shù)據(jù)既不在Redis緩存中,也不在數(shù)據(jù)庫中,導(dǎo)致請求在訪問緩存時,發(fā)生緩存缺失,再去訪問數(shù)據(jù)庫時,發(fā)現(xiàn)數(shù)據(jù)庫中也沒有要訪問的數(shù)據(jù),說明它是一個不存在的數(shù)據(jù)。
此時,應(yīng)用也無法從數(shù)據(jù)庫中讀取數(shù)據(jù)再寫入緩存,來服務(wù)后續(xù)請求,這樣一來,緩存也就成了“擺設(shè)”,如果說應(yīng)用持續(xù)有大量請求訪問數(shù)據(jù),尤其是在并發(fā)量大的場景下,這樣就會同時給緩存和數(shù)據(jù)庫帶來巨大壓力
那么,緩存穿透會發(fā)生在什么時候呢?一般來說,有兩種情況。
- 業(yè)務(wù)層誤操作:緩存中的數(shù)據(jù)和數(shù)據(jù)庫中的數(shù)據(jù)被誤刪除了,所以緩存和數(shù)據(jù)庫中都沒有數(shù)據(jù);
- 惡意攻擊:專門訪問數(shù)據(jù)庫中沒有的數(shù)據(jù)。
- 首先最簡單的方式就是緩存空值或者設(shè)置缺省值
一旦發(fā)生緩存穿透,我們就可以針對查詢的數(shù)據(jù),在Redis中緩存一個空值或是和業(yè)務(wù)層協(xié)商確定的缺省值(例如,庫存的缺省值可以設(shè)為0)。
如果是一個對象的話,也可以緩存一個空對象或者其它協(xié)商的值,對于存儲層都沒有命中請求,我們默認(rèn)返回一個業(yè)務(wù)上的對象。
這樣應(yīng)用發(fā)送的后續(xù)請求再進(jìn)行查詢時,就可以直接從Redis中讀取空值或缺省值,返回給業(yè)務(wù)應(yīng)用了,避免了把大量請求發(fā)送給數(shù)據(jù)庫處理,保持了數(shù)據(jù)庫的正常運行。
就可以抵擋大量重復(fù)沒有意義的請求,起到了保護(hù)后端的作用。
不過這個方案還是不能應(yīng)對大量高并發(fā)且不相同的緩存穿透,如果有人之前摸清楚了你業(yè)務(wù)有效范圍,一瞬間發(fā)起大量不相同的請求,你第一次查詢還是會穿透到數(shù)據(jù)庫。
另外這個方案的一種缺點就是**:每一次不同的緩存穿透,緩存一個空對象。大量不同的穿透,也就會緩存大量空對象。內(nèi)存被大量的白白占用,使真正有效的數(shù)據(jù)不能被緩存起來。**
所以對于這種方案:首先,我們要做好業(yè)務(wù)過濾。比如我們確定業(yè)務(wù)ID的范圍是[a, b],只要不屬于[a,b]的,系統(tǒng)直接返回,直接不走查詢。第二,給緩存的空對象設(shè)置一個較短的過期時間,在內(nèi)存空間不足時可以被有效快速清除。
緊接著,應(yīng)用發(fā)送的后續(xù)請求再進(jìn)行查詢時,就可以直接從Redis中讀取空值或缺省值,返回給業(yè)務(wù)應(yīng)用了,避免了把大量請求發(fā)送給數(shù)據(jù)庫處理,保持了數(shù)據(jù)庫的正常運行。
還有一種方案使用布隆過濾器,快速判斷數(shù)據(jù)是否存在,避免從數(shù)據(jù)庫中查詢數(shù)據(jù)是否存在,減輕數(shù)據(jù)庫壓力。
它的優(yōu)勢就是占用內(nèi)存空間很小,位存儲;性能特別高,使用key的hash判斷key存不存在
我們先來看下,布隆過濾器是如何工作的。
布隆過濾器由一個初值都為0的bit數(shù)組和N個哈希函數(shù)組成,可以用來快速判斷某個數(shù)據(jù)是否存在。當(dāng)我們想標(biāo)記某個數(shù)據(jù)存在時(例如,數(shù)據(jù)已被寫入數(shù)據(jù)庫),布隆過濾器會通過三個操作完成標(biāo)記:
- 首先,使用N個哈希函數(shù),分別計算這個數(shù)據(jù)的哈希值,得到N個哈希值。
- 然后,我們把這N個哈希值對bit數(shù)組的長度取模,得到每個哈希值在數(shù)組中的對應(yīng)位置。
- 最后,我們把對應(yīng)位置的bit位設(shè)置為1,這就完成了在布隆過濾器中標(biāo)記數(shù)據(jù)的操作。
如果數(shù)據(jù)不存在(例如,數(shù)據(jù)庫里沒有寫入數(shù)據(jù)),我們也就沒有用布隆過濾器標(biāo)記過數(shù)據(jù),那么,bit數(shù)組對應(yīng)bit位的值仍然為0。
當(dāng)需要查詢某個數(shù)據(jù)時,我們就執(zhí)行剛剛說的計算過程,先得到這個數(shù)據(jù)在bit數(shù)組中對應(yīng)的N個位置。緊接著,我們查看bit數(shù)組中這N個位置上的bit值。只要這N個bit值有一個不為1,這就表明布隆過濾器沒有對該數(shù)據(jù)做過標(biāo)記,所以,查詢的數(shù)據(jù)一定沒有在數(shù)據(jù)庫中保存。為了便于你理解,我畫了一張圖,如下圖所示:
圖中布隆過濾器是一個包含10個bit位的數(shù)組,使用了3個哈希函數(shù),當(dāng)在布隆過濾器中標(biāo)記數(shù)據(jù)X時,X會被計算3次哈希值,并對10取模,取模結(jié)果分別是1、3、7。所以,bit數(shù)組的第1、3、7位被設(shè)置為1。當(dāng)應(yīng)用想要查詢X時,只要查看數(shù)組的第1、3、7位是否為1,只要有一個為0,那么,X就肯定不在數(shù)據(jù)庫中。
正是基于布隆過濾器的快速檢測特性,我們可以在把數(shù)據(jù)寫入數(shù)據(jù)庫時,使用布隆過濾器做個標(biāo)記。
當(dāng)緩存缺失后,應(yīng)用查詢數(shù)據(jù)庫時,可以通過查詢布隆過濾器快速判斷數(shù)據(jù)是否存在。
如果不存在,就不用再去數(shù)據(jù)庫中查詢了。
這樣一來,即使發(fā)生緩存穿透了,大量請求只會查詢Redis和布隆過濾器,而不會積壓到數(shù)據(jù)庫,也就不會影響數(shù)據(jù)庫的正常運行。
布隆過濾器可以使用Redis實現(xiàn),本身就能承擔(dān)較大的并發(fā)訪問壓力。
最后一種方案是,在請求入口的**前端進(jìn)行請求檢測。**緩存穿透的一個原因是有大量的惡意請求訪問不存在的數(shù)據(jù),所以,一個有效的應(yīng)對方案是在請求入口前端,對業(yè)務(wù)系統(tǒng)接收到的請求進(jìn)行合法性檢測,把惡意的請求(例如請求參數(shù)不合理、請求參數(shù)是非法值、請求字段不存在)直接過濾掉,不讓它們訪問后端緩存和數(shù)據(jù)庫。這樣一來,也就不會出現(xiàn)緩存穿透問題了。
跟緩存雪崩、緩存擊穿這兩類問題相比,緩存穿透的影響更大一些,所以說同學(xué)們要重點關(guān)注一下。
從預(yù)防的角度來說,我們需要避免誤刪除數(shù)據(jù)庫和緩存中的數(shù)據(jù);從應(yīng)對角度來說,我們可以在業(yè)務(wù)系統(tǒng)中使用緩存空值或缺省值、使用布隆過濾器,以及進(jìn)行惡意請求檢測等方法。
總結(jié)一下剛才說的緩存的問題,從問題成因來看,緩存雪崩和擊穿主要是因為數(shù)據(jù)不在緩存中了,而緩存穿透則是因為數(shù)據(jù)既不在緩存中,也不在數(shù)據(jù)庫中。
所以,緩存雪崩或擊穿時,一旦數(shù)據(jù)庫中的數(shù)據(jù)被再次寫入到緩存后,應(yīng)用又可以在緩存中快速訪問數(shù)據(jù)了,數(shù)據(jù)庫的壓力也會相應(yīng)地降低下來,而緩存穿透發(fā)生時,Redis緩存和數(shù)據(jù)庫會同時持續(xù)承受請求壓力
為了方便掌握,這三大問題的原因和應(yīng)對方案總結(jié)到了一張表格
最后,我想強(qiáng)調(diào)一下,服務(wù)熔斷、服務(wù)降級、請求限流這些方法都是屬于“有損”方案,在保證數(shù)據(jù)庫和整體系統(tǒng)穩(wěn)定的同時,會對業(yè)務(wù)應(yīng)用帶來負(fù)面影響。例如使用服務(wù)降級時,有部分?jǐn)?shù)據(jù)的請求就只能得到錯誤返回信息,無法正常處理。如果使用了服務(wù)熔斷,那么,整個緩存系統(tǒng)的服務(wù)都被暫停了,影響的業(yè)務(wù)范圍更大。而使用了請求限流機(jī)制后,整個業(yè)務(wù)系統(tǒng)的吞吐率會降低,能并發(fā)處理的用戶請求會減少,會影響到用戶體驗。文章來源:http://www.zghlxwxcb.cn/news/detail-518908.html
所以說,老師工作那么長時間的經(jīng)驗或者說是建議吧,盡量使用預(yù)防式方案:文章來源地址http://www.zghlxwxcb.cn/news/detail-518908.html
- 針對緩存雪崩,合理地設(shè)置數(shù)據(jù)過期時間,以及搭建高可靠緩存集群;
- 針對緩存擊穿,在緩存訪問非常頻繁的熱點數(shù)據(jù)時,不要設(shè)置過期時間;
- 針對緩存穿透,提前在入口前端實現(xiàn)惡意請求檢測,或者規(guī)范數(shù)據(jù)庫的數(shù)據(jù)刪除操作,避免誤刪除。
到了這里,關(guān)于Redis中的緩存雪崩、擊穿、穿透的原因以及解決辦法的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!