前言
Redis是目前非常流行的緩存數(shù)據(jù)庫啦,其中一個主要作用就是為了避免大量請求直接打到數(shù)據(jù)庫,以此來緩解數(shù)據(jù)庫服務器壓力;用上緩存難道就高枕無憂了嗎?no,no,no,沒有這么完美的技術(shù), 緩存穿透、緩存雪崩、緩存擊穿這些問題都得好好聊聊。
正文
1. 緩存穿透
1.1 簡要描述
緩存穿透是指查找的數(shù)據(jù)在緩存和數(shù)據(jù)庫中都不存在,導致每一次請求數(shù)據(jù)從緩存中都獲取不到,而將請求打到數(shù)據(jù)庫服務器,但數(shù)據(jù)庫中也沒有對應的數(shù)據(jù),最后每一次請求都到數(shù)據(jù)庫;如果在高并發(fā)場景或有人惡意攻擊,就會導致后臺數(shù)據(jù)庫服務器壓力增大,最終系統(tǒng)可能崩掉。來個直接點的圖:

簡要說明:
緩存Redis服務器顏色說明:綠色塊代表有緩存數(shù)據(jù),粉色塊代表緩存中沒有數(shù)據(jù);綠色箭頭代表直接從緩存中獲取數(shù)據(jù);黃色箭頭代表穿過緩存從數(shù)據(jù)庫中查數(shù)據(jù),但不一定有。
流程大概如下:
大量客戶端發(fā)起大量請求到服務器;
服務器代碼邏輯將先經(jīng)過緩存,如果有緩存數(shù)據(jù)(綠色部分),直接從緩存中獲取數(shù)據(jù)數(shù)據(jù)返回;如果緩存中沒有數(shù)據(jù)(粉色部分),請求就會直接打到數(shù)據(jù)庫服務器(如黃色箭頭)。
如果存在大量無緩存數(shù)據(jù)的請求,最終數(shù)據(jù)庫將因為過大壓力而崩掉,導致系統(tǒng)不可用。
1.2 常用解決措施
-
緩存空值:如果沒有在數(shù)據(jù)庫中獲取到數(shù)據(jù),可以將其對應鍵的空值進行緩存,并設(shè)置較短過期時間;
優(yōu)點:在過期時間內(nèi)直接通過緩存返回空值;從而避免數(shù)據(jù)庫壓力;
缺點:
消耗Redis內(nèi)存:如果是攻擊者換著非常規(guī)的鍵值請求,如果每次都緩存到Redis中,大量的空數(shù)據(jù)也占內(nèi)存空間;
數(shù)據(jù)不一致:如果是正常數(shù)據(jù),剛開始沒有數(shù)據(jù),然后將空值進行緩存,并設(shè)置短暫的過期時間;如果在過期時間內(nèi)正常維護了對應的數(shù)據(jù),此時取到值仍是空,并沒有去數(shù)據(jù)庫中獲取新維護數(shù)據(jù),導致數(shù)據(jù)獲取不一致。
-
布隆過濾器
加一層過濾器進行攔截,判斷請求對應的鍵是否在過濾器中,如果不在就直接返回,不去請求數(shù)據(jù)庫,也不用緩存空值。而布隆過濾器采用bit位的形式標識對應鍵(每個鍵進行Hash過后都會得到具體的位置)是否存在,可以用極少的空間標識超大量的數(shù)據(jù)。
缺點:布隆過濾器可以判斷數(shù)據(jù)一定不在過濾器中,而對于存在的判斷有誤判率,因為Hash算法存在沖突的情況。
1.3 布隆過濾器
布隆過濾器不是專門用來針對緩存穿透的,它的應用場景很多,比如避免郵件重發(fā)、爬蟲軟件重爬、視頻推送重復等;可能有的小伙伴還不明白為什么可以這么用,那先簡單說說布隆過濾器的原理。
瞅個圖先:

簡要說明:
先來一個Key,后續(xù)需要判斷Key是否存在(這里Key可以是任意想存的數(shù)據(jù),比如用戶ID、視頻標識等);
將Key進行多次hash計算;每次的hash算法得到的結(jié)果都不一樣;上圖只畫了三次hash計算,其實實際根據(jù)誤判率不一樣,hash次數(shù)就不一樣;
將hash結(jié)果對應下標索引的bit位改為1,表示存在; 上圖經(jīng)過三次hash,結(jié)果分別為2、5、9,則將對應的位置改為1;
-
如果需要判斷Key是否在過濾器中,同樣需進行多次hash計算,上圖為三次,將計算出來的結(jié)果作為索引去獲取對應的標識,三次中只要有一次對應位置的值為0,那就證明Key不存在過濾器中。 如果是判定存在,則三次的結(jié)果對應位置的值應該都為1,不過這樣是有誤判可能,因為不同的Key,hash的結(jié)果有可能是一樣的,從而就導致設(shè)置對應索引位時就會有沖突,如下圖;
image-20210226105522598先假設(shè)Key1、Key2經(jīng)過三次hash的結(jié)果一樣(實際場景是存在的),倘若Key1先來都將2、5、9位置的值設(shè)為1,那Key2進來判斷存在時,由于hash的結(jié)果一樣,從而就誤判為在過濾器中,其實不存在;
誤判率在布隆過濾器中是可以控制,如果需要降低誤判率,那就多進行幾次hash計算,那位置相同的概率就降低啦;但這樣會影響效率,另外也會有內(nèi)存的額外開銷,hash次數(shù)多,需要標識的位就越多。 就算有誤判率,也很小,在絕大多數(shù)場景下可接受。
1.4 布隆過濾器的使用
既然說Redis,就說Redis的布隆過濾器吧,其實小伙伴可以根據(jù)自己的需求利用Redis的bitmap實現(xiàn)。那有沒有造好的輪子呢,當然有,在Redis4.0開始就有一個布隆過濾器的組件,開箱即用,當然也有一些其他大佬封裝的,基于內(nèi)存的,基于分布式都有。這里簡單說說Redis布隆過濾器的插件,個人覺得挺好的,推薦哦。
官方文檔地址:https://oss.redislabs.com/redisbloom/
我這面是用centos進行演示,主要步驟如下:
-
如果沒有g(shù)it的需要安裝一下;如果不用git就去下載代碼壓縮包;
yum install -y git 把redis布隆過濾器的源碼搞下來,這里用git;也可以通過下載的方式;
git clone https://github.com/RedisLabsModules/redisbloom.git
- 進入代碼目錄進行make(生成redisbloom.so文件),如果make命令找不到,就需要安裝VC++編譯相關(guān)的包;
cd redisbloom
make
-
在Redis配置文件中配置加載redisbloom插件,然后重啟就可以用啦;也可以啟動的時候指定加載插件運行;
配置文件方式式:在配置文件中添加如下配置,需要指定redisbloom.so具體的文件位置。
image-20210226112615246然后指定配置文件啟動即可;
./redis-server redis.conf
啟動時指定模塊運行方式:
./redis-server --loadmodule ./redisbloom.so
-
簡單使用
image-20210226121208096命令使用和常規(guī)命令一樣啦,就不需要我再寫程序了吧,如果非要的話,那就簡單說兩句:
A.將需要判斷數(shù)據(jù)保存在過濾器中,比如所有的用戶id;
B.當請求過來時就先從過濾器中判斷有無數(shù)據(jù),沒有直接返回,不去緩存,也不去數(shù)據(jù)庫;
C.如果有新添加的用戶,需要將新的用戶id放到過濾器中;
關(guān)于Redis布隆過濾器還有一些命令沒說,小伙伴可以去逛逛官網(wǎng)。有小伙伴說,不用這個插件行嗎,當然行啊,可以自己實現(xiàn)嘛,不過有些小伙伴有封裝好的包啦,有基于內(nèi)存的,也有基于Redis的,如下圖:

代碼我就不上了,剩下的就留給小伙伴啦。
2. 緩存雪崩
1.1 簡要描述
緩存雪崩是指突然緩存層不可用,導致大量請求直接打到數(shù)據(jù)庫,最終由于數(shù)據(jù)庫壓力過大可能導致系統(tǒng)崩掉。緩存層不可用指以下兩方面:
緩存服務器宕機,系統(tǒng)將請求打到數(shù)據(jù)庫;
緩存數(shù)據(jù)突然大范圍集中過期失效,導致大量請求打到數(shù)據(jù)庫重新加載數(shù)據(jù);
如圖:

簡要說明:
緩存Redis服務器顏色說明:綠色塊代表有緩存數(shù)據(jù),粉色塊代表緩存中沒有數(shù)據(jù);白色塊代表大范圍失效的緩存數(shù)據(jù),綠色箭頭代表直接從緩存中獲取數(shù)據(jù);黃色箭頭代表穿過緩存從數(shù)據(jù)庫中查數(shù)據(jù)。
流程大概如下:
大量客戶端發(fā)起大量請求到服務器;
服務器代碼邏輯將先經(jīng)過緩存,如果有緩存數(shù)據(jù)(綠色部分),直接從緩存中獲取數(shù)據(jù)數(shù)據(jù)返回;如果緩存過期(白色塊部分),請求就會直接打到數(shù)據(jù)庫服務器(如黃色箭頭)。
如果存在大量熱數(shù)據(jù)的請求,但熱數(shù)據(jù)又大范圍過期,最終數(shù)據(jù)庫將因為過大壓力崩掉,導致系統(tǒng)不可用。
1.2 常用解決措施
緩存預熱:在高峰期還沒到來時,提前將熱數(shù)據(jù)加載到緩存中,避免高峰期來臨時數(shù)據(jù)庫壓力過大。
均勻設(shè)置過期時間:針對不同的熱點數(shù)據(jù),將過期時間加上一個隨機值,讓過期時間不集中在一個點,從而減小很大部分數(shù)據(jù)庫壓力;
多級緩存:除了使用Redis緩存,還可以根據(jù)業(yè)務增加一些熱點數(shù)據(jù)的其他緩存,比如內(nèi)存緩存,可以將各級的緩存有效期分開,這種方式也能緩解數(shù)據(jù)庫的壓力;
限流、降級:如果壓力過大,避免把系統(tǒng)搞崩,可以增加一些限流手段,不管是中間件還是消息隊列等,主要保證系統(tǒng)的可用。
加互斥鎖:目的就是加鎖獨占操作,讓一個操作向緩存中重新加載數(shù)據(jù),讓請求操作等待,其實這樣的體驗不好,慎用。如果要用,要超級注意鎖的性能和穩(wěn)定性。
對于緩存層整體崩掉的情況:使用高可用架構(gòu),比如之前說到的主從復制、哨兵、集群,根據(jù)需求進行對應架構(gòu),保證緩存層不崩掉。
3. 緩存擊穿
1.1 簡要描述
緩存擊穿是指在超級熱點數(shù)據(jù)突然過期,導致針對超級熱點的數(shù)據(jù)請求在過期期間直接打到數(shù)據(jù)庫,這樣數(shù)據(jù)庫服務器會因為某一超熱數(shù)據(jù)導致壓力過大而崩掉。
超熱數(shù)據(jù):比如秒殺時的數(shù)據(jù),某寶、某東、某多多這種平臺的數(shù)據(jù)如果在秒殺時間段失效,請求量足矣讓數(shù)據(jù)庫崩掉。
如圖:

簡要說明:
緩存Redis服務器顏色說明:綠色塊代表有緩存數(shù)據(jù),粉色塊代表緩存中沒有數(shù)據(jù);白色圈代表超級熱點緩存數(shù)據(jù)過期失效,綠色箭頭代表直接從緩存中獲取數(shù)據(jù);黃色箭頭代表穿過緩存從數(shù)據(jù)庫中查數(shù)據(jù)。
流程大概如下:
大量客戶端發(fā)起大量請求到服務器;
服務器代碼邏輯將先經(jīng)過緩存,如果有緩存數(shù)據(jù)(綠色部分),直接從緩存中獲取數(shù)據(jù)數(shù)據(jù)返回;如果超熱緩存數(shù)據(jù)過期(白色圈部分),請求就會直接打到數(shù)據(jù)庫服務器(如黃色箭頭)。
超級熱點數(shù)據(jù)過期失效,如秒殺數(shù)據(jù),如果在秒殺時段失效,最終數(shù)據(jù)庫將因為過大壓力崩掉,導致系統(tǒng)不可用。
注:這個只是針對超熱點數(shù)據(jù),而不是大范圍數(shù)據(jù)。
1.2 常用解決措施
熱點數(shù)據(jù)不過期:像這種超熱數(shù)據(jù)就設(shè)置永不過期。避免過期失效讓數(shù)據(jù)庫壓力過大而崩。
加互斥鎖:目的就是加鎖,然后向緩存中重新加載數(shù)據(jù),讓請求等待,其實這樣的體驗不好,慎用。如果要用,要超級注意鎖的性能和穩(wěn)定性。
總結(jié)
緩存穿透、緩存雪崩、緩存擊穿不管是哪個問題,其主要原因還是在緩存層沒有命中,將請求直接打到數(shù)據(jù)庫啦,最終導致數(shù)據(jù)庫壓力過大,系統(tǒng)不可用。小伙伴根據(jù)系統(tǒng)需要進行問題處理,沒有完美的解決方案,但總會有一種適合需求的方案,解決業(yè)務問題才是真正目的。
今天沒有上代碼,相信小伙伴都能根據(jù)解決措施寫出對應的代碼,分布式鎖可能稍微有點難搞,下次抽時間給大家安排上。
關(guān)于Redis系列,下篇說說Lua腳本就算初步完成啦,剩下的就是實戰(zhàn)的總結(jié)啦,在項目的使用過程中,如果有好的方案和棘手的問題都會和小伙伴分享。接下來數(shù)據(jù)庫優(yōu)化系列即將開啟,主要針對MySql。
這篇文章特意安排在元宵發(fā)布,熬夜到兩點,就是為了祝小伙伴元宵節(jié)快樂。
一個被程序搞丑的帥小伙,關(guān)注"Code綜藝圈",跟我一起學~~~


