天天看點

Redis緩存穿透、緩存雪崩、緩存擊穿好好說說

Redis是目前非常流行的緩存資料庫啦,其中一個主要作用就是為了避免大量請求直接打到資料庫,以此來緩解資料庫伺服器壓力;用上緩存難道就高枕無憂了嗎?no,no,no,沒有這麼完美的技術, 緩存穿透、緩存雪崩、緩存擊穿這些問題都得好好聊聊。

正文1. 緩存穿透1.1 簡要描述

緩存穿透是指查找的資料在緩存和資料庫中都不存在,導緻每一次請求資料從緩存中都擷取不到,而将請求打到資料庫伺服器,但資料庫中也沒有對應的資料,最後每一次請求都到資料庫;如果在高并發場景或有人惡意攻擊,就會導緻背景資料庫伺服器壓力增大,最終系統可能崩掉。來個直接點的圖:

Redis緩存穿透、緩存雪崩、緩存擊穿好好說說

簡要說明:

緩存Redis伺服器顔色說明:綠色塊代表有緩存資料,粉色塊代表緩存中沒有資料;綠色箭頭代表直接從緩存中擷取資料;黃色箭頭代表穿過緩存從資料庫中查資料,但不一定有。

流程大概如下:

  1. 大量用戶端發起大量請求到伺服器;
  2. 伺服器代碼邏輯将先經過緩存,如果有緩存資料(綠色部分),直接從緩存中擷取資料資料傳回;如果緩存中沒有資料(粉色部分),請求就會直接打到資料庫伺服器(如黃色箭頭)。
  3. 如果存在大量無緩存資料的請求,最終資料庫将因為過大壓力而崩掉,導緻系統不可用。

1.2 常用解決措施

  • 緩存空值:如果沒有在資料庫中擷取到資料,可以将其對應鍵的空值進行緩存,并設定較短過期時間;優點:在過期時間内直接通過緩存傳回空值;進而避免資料庫壓力;缺點:消耗Redis記憶體:如果是攻擊者換着非正常的鍵值請求,如果每次都緩存到Redis中,大量的空資料也占記憶體空間;資料不一緻:如果是正常資料,剛開始沒有資料,然後将空值進行緩存,并設定短暫的過期時間;如果在過期時間内正常維護了對應的資料,此時取到值仍是空,并沒有去資料庫中擷取新維護資料,導緻資料擷取不一緻。
  • 布隆過濾器加一層過濾器進行攔截,判斷請求對應的鍵是否在過濾器中,如果不在就直接傳回,不去請求資料庫,也不用緩存空值。而布隆過濾器采用bit位的形式辨別對應鍵(每個鍵進行Hash過後都會得到具體的位置)是否存在,可以用極少的空間辨別超大量的資料。缺點:布隆過濾器可以判斷資料一定不在過濾器中,而對于存在的判斷有誤判率,因為Hash算法存在沖突的情況。

1.3 布隆過濾器

布隆過濾器不是專門用來針對緩存穿透的,它的應用場景很多,比如避免郵件重發、爬蟲軟體重爬、視訊推送重複等;可能有的小夥伴還不明白為什麼可以這麼用,那先簡單說說布隆過濾器的原理。

瞅個圖先:

Redis緩存穿透、緩存雪崩、緩存擊穿好好說說
  1. 先來一個Key,後續需要判斷Key是否存在(這裡Key可以是任意想存的資料,比如使用者ID、視訊辨別等);
  2. 将Key進行多次hash計算;每次的hash算法得到的結果都不一樣;上圖隻畫了三次hash計算,其實實際根據誤判率不一樣,hash次數就不一樣;
  3. 将hash結果對應下标索引的bit位改為1,表示存在; 上圖經過三次hash,結果分别為2、5、9,則将對應的位置改為1;
  4. 如果需要判斷Key是否在過濾器中,同樣需進行多次hash計算,上圖為三次,将計算出來的結果作為索引去擷取對應的辨別,三次中隻要有一次對應位置的值為0,那就證明Key不存在過濾器中。 如果是判定存在,則三次的結果對應位置的值應該都為1,不過這樣是有誤判可能,因為不同的Key,hash的結果有可能是一樣的,進而就導緻設定對應索引位時就會有沖突,如下圖;先假設Key1、Key2經過三次hash的結果一樣(實際場景是存在的),倘若Key1先來都将2、5、9位置的值設為1,那Key2進來判斷存在時,由于hash的結果一樣,進而就誤判為在過濾器中,其實不存在;誤判率在布隆過濾器中是可以控制,如果需要降低誤判率,那就多進行幾次hash計算,那位置相同的機率就降低啦;但這樣會影響效率,另外也會有記憶體的額外開銷,hash次數多,需要辨別的位就越多。 就算有誤判率,也很小,在絕大多數場景下可接受。

1.4 布隆過濾器的使用

既然說Redis,就說Redis的布隆過濾器吧,其實小夥伴可以根據自己的需求利用Redis的bitmap實作。那有沒有造好的輪子呢,當然有,在Redis4.0開始就有一個布隆過濾器的元件,開箱即用,當然也有一些其他大佬封裝的,基于記憶體的,基于分布式都有。這裡簡單說說Redis布隆過濾器的插件,個人覺得挺好的,推薦哦。

官方文檔位址:https://oss.redislabs.com/redisbloom/

我這面是用centos進行示範,主要步驟如下:

  1. 如果沒有git的需要安裝一下;如果不用git就去下載下傳代碼壓縮包;yum install -y git
  2. 把redis布隆過濾器的源碼搞下來,這裡用git;也可以通過下載下傳的方式;git clone https://github.com/RedisLabsModules/redisbloom.git
  3. 進入代碼目錄進行make(生成redisbloom.so檔案),如果make指令找不到,就需要安裝VC++編譯相關的包;cd redisbloom make
  4. 在Redis配置檔案中配置加載redisbloom插件,然後重新開機就可以用啦;也可以啟動的時候指定加載插件運作;配置檔案方式式:在配置檔案中添加如下配置,需要指定redisbloom.so具體的檔案位置。然後指定配置檔案啟動即可;./redis-server redis.conf 啟動時指定子產品運作方式:./redis-server --loadmodule ./redisbloom.so
  5. 簡單使用指令使用和正常指令一樣啦,就不需要我再寫程式了吧,如果非要的話,那就簡單說兩句:A.将需要判斷資料儲存在過濾器中,比如所有的使用者id;B.當請求過來時就先從過濾器中判斷有無資料,沒有直接傳回,不去緩存,也不去資料庫;C.如果有新添加的使用者,需要将新的使用者id放到過濾器中;

關于Redis布隆過濾器還有一些指令沒說,小夥伴可以去逛逛官網。有小夥伴說,不用這個插件行嗎,當然行啊,可以自己實作嘛,不過有些小夥伴有封裝好的包啦,有基于記憶體的,也有基于Redis的,如下圖:

Redis緩存穿透、緩存雪崩、緩存擊穿好好說說

代碼我就不上了,剩下的就留給小夥伴啦。

2. 緩存雪崩1.1 簡要描述

緩存雪崩是指突然緩存層不可用,導緻大量請求直接打到資料庫,最終由于資料庫壓力過大可能導緻系統崩掉。緩存層不可用指以下兩方面:

  • 緩存伺服器當機,系統将請求打到資料庫;
  • 緩存資料突然大範圍集中過期失效,導緻大量請求打到資料庫重新加載資料;

如圖:

Redis緩存穿透、緩存雪崩、緩存擊穿好好說說

緩存Redis伺服器顔色說明:綠色塊代表有緩存資料,粉色塊代表緩存中沒有資料;白色塊代表大範圍失效的緩存資料,綠色箭頭代表直接從緩存中擷取資料;黃色箭頭代表穿過緩存從資料庫中查資料。

  1. 伺服器代碼邏輯将先經過緩存,如果有緩存資料(綠色部分),直接從緩存中擷取資料資料傳回;如果緩存過期(白色塊部分),請求就會直接打到資料庫伺服器(如黃色箭頭)。
  2. 如果存在大量熱資料的請求,但熱資料又大範圍過期,最終資料庫将因為過大壓力崩掉,導緻系統不可用。
  • 緩存預熱:在高峰期還沒到來時,提前将熱資料加載到緩存中,避免高峰期來臨時資料庫壓力過大。
  • 均勻設定過期時間:針對不同的熱點資料,将過期時間加上一個随機值,讓過期時間不集中在一個點,進而減小很大部分資料庫壓力;
  • 多級緩存:除了使用Redis緩存,還可以根據業務增加一些熱點資料的其他緩存,比如記憶體緩存,可以将各級的緩存有效期分開,這種方式也能緩解資料庫的壓力;
  • 限流、降級:如果壓力過大,避免把系統搞崩,可以增加一些限流手段,不管是中間件還是消息隊列等,主要保證系統的可用。
  • 加互斥鎖:目的就是加鎖獨占操作,讓一個操作向緩存中重新加載資料,讓請求操作等待,其實這樣的體驗不好,慎用。如果要用,要超級注意鎖的性能和穩定性。
  • 對于緩存層整體崩掉的情況:使用高可用架構,比如之前說到的主從複制、哨兵、叢集,根據需求進行對應架構,保證緩存層不崩掉。

3. 緩存擊穿1.1 簡要描述

緩存擊穿是指在超級熱點資料突然過期,導緻針對超級熱點的資料請求在過期期間直接打到資料庫,這樣資料庫伺服器會因為某一超熱資料導緻壓力過大而崩掉。

超熱資料:比如秒殺時的資料,某寶、某東、某多多這種平台的資料如果在秒殺時間段失效,請求量足矣讓資料庫崩掉。

Redis緩存穿透、緩存雪崩、緩存擊穿好好說說

緩存Redis伺服器顔色說明:綠色塊代表有緩存資料,粉色塊代表緩存中沒有資料;白色圈代表超級熱點緩存資料過期失效,綠色箭頭代表直接從緩存中擷取資料;黃色箭頭代表穿過緩存從資料庫中查資料。

  1. 伺服器代碼邏輯将先經過緩存,如果有緩存資料(綠色部分),直接從緩存中擷取資料資料傳回;如果超熱緩存資料過期(白色圈部分),請求就會直接打到資料庫伺服器(如黃色箭頭)。
  2. 超級熱點資料過期失效,如秒殺資料,如果在秒殺時段失效,最終資料庫将因為過大壓力崩掉,導緻系統不可用。

注:這個隻是針對超熱點資料,而不是大範圍資料。

  • 熱點資料不過期:像這種超熱資料就設定永不過期。避免過期失效讓資料庫壓力過大而崩。
  • 加互斥鎖:目的就是加鎖,然後向緩存中重新加載資料,讓請求等待,其實這樣的體驗不好,慎用。如果要用,要超級注意鎖的性能和穩定性。

總結

繼續閱讀