redis緩存穿透盛泡,緩存擊穿,緩存雪崩原因+解決方案

一娱颊、前言

在我們?nèi)粘5拈_發(fā)中傲诵,無不都是使用數(shù)據(jù)庫來進(jìn)行數(shù)據(jù)的存儲,由于一般的系統(tǒng)任務(wù)中通常不會存在高并發(fā)的情況箱硕,所以這樣看起來并沒有什么問題拴竹,可是一旦涉及大數(shù)據(jù)量的需求,比如一些商品搶購的情景剧罩,或者是主頁訪問量瞬間較大的時(shí)候栓拜,單一使用數(shù)據(jù)庫來保存數(shù)據(jù)的系統(tǒng)會因?yàn)槊嫦虼疟P,磁盤讀/寫速度比較慢的問題而存在嚴(yán)重的性能弊端惠昔,一瞬間成千上萬的請求到來幕与,需要系統(tǒng)在極短的時(shí)間內(nèi)完成成千上萬次的讀/寫操作,這個(gè)時(shí)候往往不是數(shù)據(jù)庫能夠承受的镇防,極其容易造成數(shù)據(jù)庫系統(tǒng)癱瘓啦鸣,最終導(dǎo)致服務(wù)宕機(jī)的嚴(yán)重生產(chǎn)問題。

為了克服上述的問題来氧,項(xiàng)目通常會引入NoSQL技術(shù)诫给,這是一種基于內(nèi)存的數(shù)據(jù)庫,并且提供一定的持久化功能啦扬。

redis技術(shù)就是NoSQL技術(shù)中的一種中狂,但是引入redis又有可能出現(xiàn)緩存穿透,緩存擊穿扑毡,緩存雪崩等問題胃榕。本文就對這三種問題進(jìn)行較深入剖析。

二僚楞、初認(rèn)識

  • 緩存穿透:key對應(yīng)的數(shù)據(jù)在數(shù)據(jù)源并不存在勤晚,每次針對此key的請求從緩存獲取不到,請求都會到數(shù)據(jù)源泉褐,從而可能壓垮數(shù)據(jù)源赐写。比如用一個(gè)不存在的用戶id獲取用戶信息,不論緩存還是數(shù)據(jù)庫都沒有膜赃,若黑客利用此漏洞進(jìn)行攻擊可能壓垮數(shù)據(jù)庫挺邀。
  • 緩存擊穿:key對應(yīng)的數(shù)據(jù)存在,但在redis中過期,此時(shí)若有大量并發(fā)請求過來端铛,這些請求發(fā)現(xiàn)緩存過期一般都會從后端DB加載數(shù)據(jù)并回設(shè)到緩存泣矛,這個(gè)時(shí)候大并發(fā)的請求可能會瞬間把后端DB壓垮。
  • 緩存雪崩:當(dāng)緩存服務(wù)器重啟或者大量緩存集中在某一個(gè)時(shí)間段失效禾蚕,這樣在失效的時(shí)候您朽,也會給后端系統(tǒng)(比如DB)帶來很大壓力。

三换淆、緩存穿透解決方案

一個(gè)一定不存在緩存及查詢不到的數(shù)據(jù)哗总,由于緩存是不命中時(shí)被動寫的,并且出于容錯(cuò)考慮倍试,如果從存儲層查不到數(shù)據(jù)則不寫入緩存讯屈,這將導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請求都要到存儲層去查詢,失去了緩存的意義县习。

有很多種方法可以有效地解決緩存穿透問題涮母,最常見的則是采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中躁愿,一個(gè)一定不存在的數(shù)據(jù)會被 這個(gè)bitmap攔截掉叛本,從而避免了對底層存儲系統(tǒng)的查詢壓力。另外也有一個(gè)更為簡單粗暴的方法(我們采用的就是這種)攘已,如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在炮赦,還是系統(tǒng)故障),我們?nèi)匀话堰@個(gè)空結(jié)果進(jìn)行緩存样勃,但它的過期時(shí)間會很短,最長不超過五分鐘性芬。

粗暴方式偽代碼:

//偽代碼
public object GetProductListNew() {
    int cacheTime = 30;
    String cacheKey = "product_list";

    String cacheValue = CacheHelper.Get(cacheKey);
    if (cacheValue != null) {
        return cacheValue;
    }

    cacheValue = CacheHelper.Get(cacheKey);
    if (cacheValue != null) {
        return cacheValue;
    } else {
        //數(shù)據(jù)庫查詢不到峡眶,為空
        cacheValue = GetProductListFromDB();
        if (cacheValue == null) {
            //如果發(fā)現(xiàn)為空,設(shè)置個(gè)默認(rèn)值植锉,也緩存起來
            cacheValue = string.Empty;
        }
        CacheHelper.Add(cacheKey, cacheValue, cacheTime);
        return cacheValue;
    }
}

四辫樱、緩存擊穿解決方案

key可能會在某些時(shí)間點(diǎn)被超高并發(fā)地訪問,是一種非晨”樱“熱點(diǎn)”的數(shù)據(jù)狮暑。這個(gè)時(shí)候,需要考慮一個(gè)問題:緩存被“擊穿”的問題辉饱。

使用互斥鎖(mutex key)

業(yè)界比較常用的做法搬男,是使用mutex。簡單地來說彭沼,就是在緩存失效的時(shí)候(判斷拿出來的值為空)缔逛,不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者M(jìn)emcache的ADD)去set一個(gè)mutex key,當(dāng)操作返回成功時(shí)褐奴,再進(jìn)行l(wèi)oad db的操作并回設(shè)緩存按脚;否則,就重試整個(gè)get緩存的方法敦冬。

SETNX辅搬,是「SET if Not eXists」的縮寫,也就是只有不存在的時(shí)候才設(shè)置脖旱,可以利用它來實(shí)現(xiàn)鎖的效果堪遂。

public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表緩存值過期
          //設(shè)置3min的超時(shí),防止del操作失敗的時(shí)候夯缺,下次緩存過期一直不能load db
      if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表設(shè)置成功
               value = db.get(key);
                      redis.set(key, value, expire_secs);
                      redis.del(key_mutex);
              } else {  //這個(gè)時(shí)候代表同時(shí)候的其他線程已經(jīng)load db并回設(shè)到緩存了蚤氏,這時(shí)候重試獲取緩存值即可
                      sleep(50);
                      get(key);  //重試
              }
          } else {
              return value;      
          }
 }

memcache代碼:

if (memcache.get(key) == null) {  
    // 3 min timeout to avoid mutex holder crash  
    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
        value = db.get(key);  
        memcache.set(key, value);  
        memcache.delete(key_mutex);  
    } else {  
        sleep(50);  
        retry();  
    }  
}

其它方案:待各位補(bǔ)充。

五踊兜、緩存雪崩解決方案

與緩存擊穿的區(qū)別在于這里針對很多key緩存竿滨,前者則是某一個(gè)key。

緩存正常從Redis中獲取捏境,示意圖如下:


緩存失效瞬間示意圖如下:


緩存失效時(shí)的雪崩效應(yīng)對底層系統(tǒng)的沖擊非秤谟危可怕!大多數(shù)系統(tǒng)設(shè)計(jì)者考慮用加鎖或者隊(duì)列的方式保證來保證不會有大量的線程對數(shù)據(jù)庫一次性進(jìn)行讀寫垫言,從而避免失效時(shí)大量的并發(fā)請求落到底層存儲系統(tǒng)上贰剥。還有一個(gè)簡單方案就時(shí)講緩存失效時(shí)間分散開,比如我們可以在原有的失效時(shí)間基礎(chǔ)上增加一個(gè)隨機(jī)值筷频,比如1-5分鐘隨機(jī)蚌成,這樣每一個(gè)緩存的過期時(shí)間的重復(fù)率就會降低,就很難引發(fā)集體失效的事件凛捏。

加鎖排隊(duì)担忧,偽代碼如下:

//偽代碼
public object GetProductListNew() {
    int cacheTime = 30;
    String cacheKey = "product_list";
    String lockKey = cacheKey;

    String cacheValue = CacheHelper.get(cacheKey);
    if (cacheValue != null) {
        return cacheValue;
    } else {
        synchronized(lockKey) {
            cacheValue = CacheHelper.get(cacheKey);
            if (cacheValue != null) {
                return cacheValue;
            } else {
              //這里一般是sql查詢數(shù)據(jù)
                cacheValue = GetProductListFromDB(); 
                CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            }
        }
        return cacheValue;
    }
}

加鎖排隊(duì)只是為了減輕數(shù)據(jù)庫的壓力,并沒有提高系統(tǒng)吞吐量坯癣。假設(shè)在高并發(fā)下瓶盛,緩存重建期間key是鎖著的,這是過來1000個(gè)請求999個(gè)都在阻塞的示罗。同樣會導(dǎo)致用戶等待超時(shí)惩猫,這是個(gè)治標(biāo)不治本的方法!

注意:加鎖排隊(duì)的解決方式分布式環(huán)境的并發(fā)問題蚜点,有可能還要解決分布式鎖的問題轧房;線程還會被阻塞,用戶體驗(yàn)很差禽额!因此锯厢,在真正的高并發(fā)場景下很少使用皮官!

隨機(jī)值偽代碼:

//偽代碼
public object GetProductListNew() {
    int cacheTime = 30;
    String cacheKey = "product_list";
    //緩存標(biāo)記
    String cacheSign = cacheKey + "_sign";

    String sign = CacheHelper.Get(cacheSign);
    //獲取緩存值
    String cacheValue = CacheHelper.Get(cacheKey);
    if (sign != null) {
        return cacheValue; //未過期,直接返回
    } else {
        CacheHelper.Add(cacheSign, "1", cacheTime);
        ThreadPool.QueueUserWorkItem((arg) -> {
      //這里一般是 sql查詢數(shù)據(jù)
            cacheValue = GetProductListFromDB(); 
          //日期設(shè)緩存時(shí)間的2倍实辑,用于臟讀
          CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);                 
        });
        return cacheValue;
    }
} 

解釋說明:

  • 緩存標(biāo)記:記錄緩存數(shù)據(jù)是否過期捺氢,如果過期會觸發(fā)通知另外的線程在后臺去更新實(shí)際key的緩存;
  • 緩存數(shù)據(jù):它的過期時(shí)間比緩存標(biāo)記的時(shí)間延長1倍剪撬,例:標(biāo)記緩存時(shí)間30分鐘摄乒,數(shù)據(jù)緩存設(shè)置為60分鐘。這樣残黑,當(dāng)緩存標(biāo)記key過期后馍佑,實(shí)際緩存還能把舊數(shù)據(jù)返回給調(diào)用端,直到另外的線程在后臺更新完成后梨水,才會返回新緩存拭荤。

關(guān)于緩存崩潰的解決方法,這里提出了三種方案:使用鎖或隊(duì)列疫诽、設(shè)置過期標(biāo)志更新緩存舅世、為key設(shè)置不同的緩存失效時(shí)間,還有一種被稱為“二級緩存”的解決方法奇徒。

六雏亚、小結(jié)

針對業(yè)務(wù)系統(tǒng),永遠(yuǎn)都是具體情況具體分析摩钙,沒有最好罢低,只有最合適。

于緩存其它問題胖笛,緩存滿了和數(shù)據(jù)丟失等問題网持,大伙可自行學(xué)習(xí)。最后也提一下三個(gè)詞LRU长踊、RDB翎碑、AOF,通常我們采用LRU策略處理溢出之斯,Redis的RDB和AOF持久化策略來保證一定情況下的數(shù)據(jù)安全。

參考相關(guān)鏈接:

https://blog.csdn.net/zeb_perfect/article/details/54135506
https://blog.csdn.net/fanrenxiang/article/details/80542580
https://baijiahao.baidu.com/s?id=1619572269435584821&wfr=spider&for=pc
https://blog.csdn.net/xlgen157387/article/details/79530877

視頻資源獲取遣铝,可直進(jìn)百度云群:

https://pan.baidu.com/mbox/homepage?short=btNBJoN

本文在米兜公眾號鏈接

https://mp.weixin.qq.com/s/ksVC1049wZgPIOy2gGziNA

歡迎關(guān)注米兜Java佑刷,一個(gè)注在共享、交流的Java學(xué)習(xí)平臺酿炸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘫絮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子填硕,更是在濱河造成了極大的恐慌麦萤,老刑警劉巖鹿鳖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異壮莹,居然都是意外死亡翅帜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門命满,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涝滴,“玉大人,你說我怎么就攤上這事胶台〖叽” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵诈唬,是天一觀的道長韩脏。 經(jīng)常有香客問我,道長铸磅,這世上最難降的妖魔是什么赡矢? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮愚屁,結(jié)果婚禮上济竹,老公的妹妹穿的比我還像新娘。我一直安慰自己霎槐,他們只是感情好送浊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著丘跌,像睡著了一般袭景。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闭树,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天耸棒,我揣著相機(jī)與錄音,去河邊找鬼报辱。 笑死与殃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的碍现。 我是一名探鬼主播幅疼,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼昼接!你這毒婦竟也來了爽篷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤慢睡,失蹤者是張志新(化名)和其女友劉穎逐工,沒想到半個(gè)月后铡溪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泪喊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年棕硫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窘俺。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡形耗,死狀恐怖胃珍,靈堂內(nèi)的尸體忽然破棺而出辱匿,到底是詐尸還是另有隱情硬猫,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布对途,位于F島的核電站赦邻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏实檀。R本人自食惡果不足惜惶洲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膳犹。 院中可真熱鬧恬吕,春花似錦、人聲如沸须床。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豺旬。三九已至钠惩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間族阅,已是汗流浹背篓跛。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坦刀,地道東北人愧沟。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像鲤遥,于是被迫代替她去往敵國和親央渣。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容