<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          緩存穿透、緩存擊穿、緩存雪崩解決方案

          共 4238字,需瀏覽 9分鐘

           ·

          2021-03-27 11:09


          前言


          我一個QPS不到10的項目,天天問我緩存穿透、緩存擊穿、緩存雪崩,我是真滴難。



          可能大家經(jīng)常會有這種感受,但是只要是面試要問的題目,就算用不上,我們也要去學習和了解,誰叫我們窮了。




          正文


          緩存穿透


          描述:訪問一個緩存和數(shù)據(jù)庫都不存在的 key,此時會直接打到數(shù)據(jù)庫上,并且查不到數(shù)據(jù),沒法寫緩存,所以下一次同樣會打到數(shù)據(jù)庫上。


          此時,緩存起不到作用,請求每次都會走到數(shù)據(jù)庫,流量大時數(shù)據(jù)庫可能會被打掛。此時緩存就好像被“穿透”了一樣,起不到任何作用。


          解決方案:


          1、接口校驗在正常業(yè)務流程中可能會存在少量訪問不存在 key 的情況,但是一般不會出現(xiàn)大量的情況,所以這種場景最大的可能性是遭受了非法攻擊。可以在最外層先做一層校驗:用戶鑒權(quán)、數(shù)據(jù)合法性校驗等,例如商品查詢中,商品的ID是正整數(shù),則可以直接對非正整數(shù)直接過濾等等。


          2、緩存空值。當訪問緩存和DB都沒有查詢到值時,可以將空值寫進緩存,但是設(shè)置較短的過期時間,該時間需要根據(jù)產(chǎn)品業(yè)務特性來設(shè)置。


          3、布隆過濾器。使用布隆過濾器存儲所有可能訪問的 key,不存在的 key 直接被過濾,存在的 key 則再進一步查詢緩存和數(shù)據(jù)庫。



          布隆過濾器


          布隆過濾器的特點是判斷不存在的,則一定不存在;判斷存在的,大概率存在,但也有小概率不存在。并且這個概率是可控的,我們可以讓這個概率變小或者變高,取決于用戶本身的需求。


          布隆過濾器由一個 bitSet 和 一組 Hash 函數(shù)(算法)組成,是一種空間效率極高的概率型算法和數(shù)據(jù)結(jié)構(gòu),主要用來判斷一個元素是否在集合中存在。


          在初始化時,bitSet 的每一位被初始化為0,同時會定義 Hash 函數(shù),例如有3組 Hash 函數(shù):hash1、hash2、hash3。


          寫入流程

          當我們要寫入一個值時,過程如下,“jionghui”為例


          1)首先將“jionghui”跟3組 Hash 函數(shù)分別計算,得到 bitSet 的下標為:1、7、10。


          2)將 bitSet 的這3個下標標記為1。


          假設(shè)我們還有另外兩個值:java 和 diaosi,按上面的流程跟 3組 Hash 函數(shù)分別計算,結(jié)果如下:


          java:Hash 函數(shù)計算 bitSet 下標為:1、7、11


          diaosi:Hash 函數(shù)計算  bitSet 下標為:4、10、11



          查詢流程

          當我們要查詢一個值時,過程如下,同樣“jionghui”為例


          1)首先將“jionghui”跟3組 Hash 函數(shù)分別計算,得到 bitSet 的下標為:1、7、10。


          2)查看 bitSet 的這3個下標是否都為1,如果這3個下標不都為1,則說明該值必然不存在,如果這3個下標都為1,則只能說明可能存在,并不能說明一定存在。


          其實上圖的例子已經(jīng)說明了這個問題了,當我們只有值“jionghui”和“diaosi”時,bitSet 下標為1的有:1、4、7、10、11。


          當我們又加入值“java”時,bitSet 下標為1的還是這5個,所以當 bitSet 下標為1的為:1、4、7、10、11 時,我們無法判斷值“java”存不存在。


          其根本原因是,不同的值在跟 Hash 函數(shù)計算后,可能會得到相同的下標,所以某個值的標記位,可能會被其他值給標上了。


          這也是為啥布隆過濾器只能判斷某個值可能存在,無法判斷必然存在的原因。但是反過來,如果該值根據(jù) Hash 函數(shù)計算的標記位沒有全部都為1,那么則說明必然不存在,這個是肯定的。


          降低這種誤判率的思路也比較簡單:


          1)一個是加大 bitSet 的長度,這樣不同的值出現(xiàn)“沖突”的概率就降低了,從而誤判率也降低。


          2)提升 Hash 函數(shù)的個數(shù),Hash 函數(shù)越多,每個值對應的 bit 越多,從而誤判率也降低。


          布隆過濾器的誤判率還有專門的推導公式,有興趣的可以去搜相關(guān)的文章和論文查看。



          HashMap 和 布隆過濾器


          估計有同學看了上面的例子,會覺得使用 HashMap 也能實現(xiàn)。


          確實,當數(shù)據(jù)量不大時,HashMap 實現(xiàn)起來一點問題都沒有,而且還沒有誤判率,簡直完美,還要個雞兒布隆過濾器。



          不過,當數(shù)據(jù)量上去后,布隆過濾器的空間優(yōu)勢就會開始體現(xiàn),特別是要存儲的 key 占用空間越大,布隆過濾器的優(yōu)勢越明顯。


          Guava 中的 BloomFilter 在默認情況下,誤判率接近3%,大概使用5 Hash 函數(shù)。


          也就是說一個 key 最多占用空間就是 5 bit,而且當多個 key 填充同一個 bit 時,會進一步降低使用空間。


          布隆過濾器占用多少空間,主要取決于 Hash 函數(shù)的個數(shù),跟 key 本身的大小無關(guān),這使得其在空間的優(yōu)勢非常大。



          緩存擊穿


          描述:某一個熱點 key,在緩存過期的一瞬間,同時有大量的請求打進來,由于此時緩存過期了,所以請求最終都會走到數(shù)據(jù)庫,造成瞬時數(shù)據(jù)庫請求量大、壓力驟增,甚至可能打垮數(shù)據(jù)庫。


          解決方案:


          1、加互斥鎖。在并發(fā)的多個請求中,只有第一個請求線程能拿到鎖并執(zhí)行數(shù)據(jù)庫查詢操作,其他的線程拿不到鎖就阻塞等著,等到第一個線程將數(shù)據(jù)寫入緩存后,直接走緩存。


          關(guān)于互斥鎖的選擇,網(wǎng)上看到的大部分文章都是選擇 Redis 分布式鎖(可以參考我之前的文章:面試必問的分布式鎖,你懂了嗎?),因為這個可以保證只有一個請求會走到數(shù)據(jù)庫,這是一種思路。


          但是其實仔細想想的話,這邊其實沒有必要保證只有一個請求走到數(shù)據(jù)庫,只要保證走到數(shù)據(jù)庫的請求能大大降低即可,所以還有另一個思路是 JVM 鎖。


          JVM 鎖保證了在單臺服務器上只有一個請求走到數(shù)據(jù)庫,通常來說已經(jīng)足夠保證數(shù)據(jù)庫的壓力大大降低,同時在性能上比分布式鎖更好。


          需要注意的是,無論是使用“分布式鎖”,還是“JVM 鎖”,加鎖時要按 key 維度去加鎖。


          我看網(wǎng)上很多文章都是使用一個“定的 key”加鎖,這樣會導致不同的 key 之間也會互相阻塞,造成性能嚴重損耗。


          使用 redis 分布式鎖的偽代碼,僅供參考:

          public Object getData(String key) throws InterruptedException {    Object value = redis.get(key);    // 緩存值過期    if (value == null) {        // lockRedis:專門用于加鎖的redis;        // "empty":加鎖的值隨便設(shè)置都可以        if (lockRedis.set(key, "empty", "PX", lockExpire, "NX")) {            try {                // 查詢數(shù)據(jù)庫,并寫到緩存,讓其他線程可以直接走緩存                value = getDataFromDb(key);                redis.set(key, value, "PX", expire);            } catch (Exception e) {                // 異常處理            } finally {                // 釋放鎖                lockRedis.delete(key);            }        } else {            // sleep50ms后,進行重試            Thread.sleep(50);            return getData(key);        }    }    return value;}

          2、熱點數(shù)據(jù)不過期。直接將緩存設(shè)置為不過期,然后由定時任務去異步加載數(shù)據(jù),更新緩存。


          這種方式適用于比較極端的場景,例如流量特別特別大的場景,使用時需要考慮業(yè)務能接受數(shù)據(jù)不一致的時間,還有就是異常情況的處理,不要到時候緩存刷新不上,一直是臟數(shù)據(jù),那就涼了。



          緩存雪崩


          描述:大量的熱點 key 設(shè)置了相同的過期時間,導在緩存在同一時刻全部失效,造成瞬時數(shù)據(jù)庫請求量大、壓力驟增,引起雪崩,甚至導致數(shù)據(jù)庫被打掛。


          緩存雪崩其實有點像“升級版的緩存擊穿”,緩存擊穿是一個熱點 key,緩存雪崩是一組熱點 key。


          解決方案:


          1、過期時間打散。既然是大量緩存集中失效,那最容易想到就是讓他們不集中生效。可以給緩存的過期時間時加上一個隨機值時間,使得每個 key 的過期時間分布開來,不會集中在同一時刻失效。


          2、熱點數(shù)據(jù)不過期該方式和緩存擊穿樣,也是要著重考慮刷新的時間間隔和數(shù)據(jù)異常如何處理的情況。


          3、加互斥鎖。該方式和緩存擊穿一樣,按 key 維度加鎖,對于同一個 key,只允許一個線程去計算,其他線程原地阻塞等待第一個線程的計算結(jié)果,然后直接走緩存即可。



          最后


          金三銀四的季節(jié),相信有不少同學正準備跳槽。 


          我將我最近的原創(chuàng)的文章進行了匯總,其中有不少面試高頻題目解析,很多都是我自己在面試大廠時遇到的,我在對每個題目解析時都會按較高的標準進行深入探討,可能只看一遍并不能完全明白,但是相信反復閱讀,定能有所收獲。


          有興趣的同學可以在我公眾號底部的“原創(chuàng)匯總”自行查看。


          原創(chuàng)不易,如果你覺得本文寫的還不錯,對你有幫助,請通過【點贊】讓我知道,支持我寫出更好的文章。



          推薦閱讀


          兩年Java開發(fā)工作經(jīng)驗面試總結(jié)

          4 年 Java 經(jīng)驗面試總結(jié)、心得體會

          5 年 Java 經(jīng)驗字節(jié)、美團、快手核心部門面試總結(jié)(真題解析)

          面試必問的 Spring,你懂了嗎?

          如何寫一份讓 HR 眼前一亮的簡歷(附模板)

          面試阿里,HashMap 這一篇就夠了

          面試必問的線程池,你懂了嗎?

          BATJTMD 面試必問的 MySQL,你懂了嗎?

          如何準備好一場大廠面試

          跳槽,如何選擇一家公司

          瀏覽 59
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品九九九九 | 美国风流航班成人版 | 久久性精品 | 18禁黄网站网址免费入口 | 强伦人妻一区二区三区视频 |