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

前言

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

正文

1. 緩存穿透

1.1 簡要描述

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

image-20210226101420703

簡要說明:

緩存Redis服務器顏色說明:綠色塊代表有緩存數據,粉色塊代表緩存中沒有數據;綠色箭頭代表直接從緩存中獲取數據;黃色箭頭代表穿過緩存從數據庫中查數據,但不一定有。

流程大概如下:

  1. 大量客戶端發起大量請求到服務器;
  2. 服務器代碼邏輯將先經過緩存,如果有緩存數據(綠色部分),直接從緩存中獲取數據數據返回;如果緩存中沒有數據(粉色部分),請求就會直接打到數據庫服務器(如黃色箭頭)。
  3. 如果存在大量無緩存數據的請求,最終數據庫將因為過大壓力而崩掉,導致系統不可用。
1.2 常用解決措施
  • 緩存空值:如果沒有在數據庫中獲取到數據,可以將其對應鍵的空值進行緩存,並設置較短過期時間;

    優點:在過期時間內直接通過緩存返回空值;從而避免數據庫壓力;

    缺點:

    消耗Redis內存:如果是攻擊者換着非常規的鍵值請求,如果每次都緩存到Redis中,大量的空數據也占內存空間;

    數據不一致:如果是正常數據,剛開始沒有數據,然後將空值進行緩存,並設置短暫的過期時間;如果在過期時間內正常維護了對應的數據,此時取到值仍是空,並沒有去數據庫中獲取新維護數據,導致數據獲取不一致。

  • 布隆過濾器

    加一層過濾器進行攔截,判斷請求對應的鍵是否在過濾器中,如果不在就直接返回,不去請求數據庫,也不用緩存空值。而布隆過濾器採用bit位的形式標識對應鍵(每個鍵進行Hash過後都會得到具體的位置)是否存在,可以用極少的空間標識超大量的數據。

    缺點:布隆過濾器可以判斷數據一定不在過濾器中,而對於存在的判斷有誤判率,因為Hash算法存在衝突的情況。

1.3 布隆過濾器

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

瞅個圖先:

image-20210226102820492

簡要說明:

  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的結果有可能是一樣的,從而就導致設置對應索引位時就會有衝突,如下圖;

    image-20210226105522598

    先假設Key1、Key2經過三次hash的結果一樣(實際場景是存在的),倘若Key1先來都將2、5、9位置的值設為1,那Key2進來判斷存在時,由於hash的結果一樣,從而就誤判為在過濾器中,其實不存在;

    誤判率在布隆過濾器中是可以控制,如果需要降低誤判率,那就多進行幾次hash計算,那位置相同的概率就降低啦;但這樣會影響效率,另外也會有內存的額外開銷,hash次數多,需要標識的位就越多。 就算有誤判率,也很小,在絕大多數場景下可接受。

1.4 布隆過濾器的使用

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

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

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

  1. 如果沒有git的需要安裝一下;如果不用git就去下載代碼壓縮包;

    yum install -y git
    
  2. 把redis布隆過濾器的源碼搞下來,這裡用git;也可以通過下載的方式;

    git clone //github.com/RedisLabsModules/redisbloom.git
    
  3. 進入代碼目錄進行make(生成redisbloom.so文件),如果make命令找不到,就需要安裝VC++編譯相關的包;

    cd redisbloom
    make
    
  4. 在Redis配置文件中配置加載redisbloom插件,然後重啟就可以用啦;也可以啟動的時候指定加載插件運行;

    配置文件方式式:在配置文件中添加如下配置,需要指定redisbloom.so具體的文件位置。

    image-20210226112615246

    然後指定配置文件啟動即可;

    ./redis-server redis.conf
    

    啟動時指定模塊運行方式:

    ./redis-server --loadmodule ./redisbloom.so
    
  5. 簡單使用

    image-20210226121208096

    命令使用和常規命令一樣啦,就不需要我再寫程序了吧,如果非要的話,那就簡單說兩句:

    A.將需要判斷數據保存在過濾器中,比如所有的用戶id;

    B.當請求過來時就先從過濾器中判斷有無數據,沒有直接返回,不去緩存,也不去數據庫;

    C.如果有新添加的用戶,需要將新的用戶id放到過濾器中;

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

image-20210226122058230

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

2. 緩存雪崩

1.1 簡要描述

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

  • 緩存服務器宕機,系統將請求打到數據庫;
  • 緩存數據突然大範圍集中過期失效,導致大量請求打到數據庫重新加載數據;

如圖:

image-20210226122232085

簡要說明:

緩存Redis服務器顏色說明:綠色塊代表有緩存數據,粉色塊代表緩存中沒有數據;白色塊代表大範圍失效的緩存數據,綠色箭頭代表直接從緩存中獲取數據;黃色箭頭代表穿過緩存從數據庫中查數據。

流程大概如下:

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

3. 緩存擊穿

1.1 簡要描述

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

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

如圖:

image-20210226123740394

簡要說明:

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

流程大概如下:

  1. 大量客戶端發起大量請求到服務器;
  2. 服務器代碼邏輯將先經過緩存,如果有緩存數據(綠色部分),直接從緩存中獲取數據數據返回;如果超熱緩存數據過期(白色圈部分),請求就會直接打到數據庫服務器(如黃色箭頭)
  3. 超級熱點數據過期失效,如秒殺數據,如果在秒殺時段失效,最終數據庫將因為過大壓力崩掉,導致系統不可用。

註:這個只是針對超熱點數據,而不是大範圍數據。

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

總結

緩存穿透、緩存雪崩、緩存擊穿不管是哪個問題,其主要原因還是在緩存層沒有命中,將請求直接打到數據庫啦,最終導致數據庫壓力過大,系統不可用。小夥伴根據系統需要進行問題處理,沒有完美的解決方案,但總會有一種適合需求的方案,解決業務問題才是真正目的。

今天沒有上代碼,相信小夥伴都能根據解決措施寫出對應的代碼,分佈式鎖可能稍微有點難搞,下次抽時間給大家安排上。

關於Redis系列,下篇說說Lua腳本就算初步完成啦,剩下的就是實戰的總結啦,在項目的使用過程中,如果有好的方案和棘手的問題都會和小夥伴分享。接下來數據庫優化系列即將開啟,主要針對MySql。

這篇文章特意安排在元宵發佈,熬夜到兩點,就是為了祝小夥伴元宵節快樂。

一個被程序搞丑的帥小伙,關注”Code綜藝圈”,跟我一起學~~~