當前位置: 妍妍網 > 碼農

Redis網紅高頻面試題三連:緩存穿透?緩存擊穿?緩存雪崩?

2024-03-07碼農

作者:熬夜不加班
連結:https://www.jianshu.com/p/9da8133be99f

Redis網紅面試題三連

  • 面試題1:怎麽解決緩存穿透問題的?

  • 面試題2:說一下緩存擊穿吧,你們是怎麽解決的?

  • 面試題3:那緩存雪崩說說你們是怎麽解決的?

  • 面試題1:怎麽解決緩存穿透問題的?

    緩存穿透:指緩存和資料庫中都沒有的數據,導致所有的請求都打到資料庫上,然後資料庫還查不到(如null),沒法寫緩存,造成資料庫短時間執行緒數被打滿而導致其他服務阻塞,最終導致線上服務不可用。此時緩存就好像被穿透了一樣,起不到任何作用。

    當然,使用緩存難免會有穿透的發生。

  • 緩存容量有限,不可能去緩存所有數據,查詢到未被緩存的數據就會發生穿透是正常情況。

  • 互聯網業務的數據存取模型一般是遵循二八原則的,即 20% 的數據為熱點數據,80% 的數據是非熱點不被常存取的數據。既然緩存容量有限,且20%的數據為熱點數據,那我們可以利用有限的容量去緩存那 20% 的數據來保護我們的系統,至於80%非熱點不常用的數據發生穿透就穿透了,資料庫吃得住。

  • 那我們怎樣來解決這種緩存穿透問題呢?

  • 介面參數校驗:

  • 防君子不防小人。在參數校驗層加上參數合法性校驗,如查詢訂單ID為20位隨機值,正則核對一下ID長度是否規範,不規範地直接過濾掉。

  • 設定空值:

  • 當存取緩存和DB都沒有查詢到值時,該key我們當做是惡意參數來看,可以將該key的空值寫進緩存,設定較短的過期時間。

    但是如果有大量的獲取並不存在數據的穿透請求的話如惡意攻擊,則會浪費緩存空間,如果這種null值過量的話,還會淘汰掉本身緩存存在的數據,這就會使我們的緩存命中率下降。

    因此在使用設定空值方案時,我們要做好監控,預防緩存空間被過多null值占領造成的緩存空間浪費,如果這種數據量太大,就不再建議使用,那就使用另一種方案,即布隆過濾器。

  • 布隆過濾器:

  • 布隆過濾器在查詢緩存之前起到初步過濾作用,布隆過濾器儲存所有可能存取的 key,將不存在的 key 直接過濾,存在的 key 再進一步查詢緩存和資料庫。

    布隆過濾器的特點是判斷不存在的,則一定不存在;判斷存在的,大機率存在,但也有小機率不存在。並且這個機率是可控的,根據具體需求,我們可以讓這個機率小幅降低或變高。

    布隆過濾器由一個 bitSet 和 一組 Hash 函式(演算法)組成,是一種空間效率極高的機率型演算法和數據結構,透過二進制來進行數據儲存。在初始化時,bitSet 的每一位被初始化為0。

    當數據加入布隆過濾器集合時,流程如下:

  • 經過K個哈希函式計算該數據,返回K個計算出的hash值

  • 這些K個hash值對映到對應的K個二進制的陣列下標

  • 將K個下標對應的二進制數據改為1。

  • 布隆過濾器查詢一個key是否在集合中,流程如下:

  • 經過K個哈希函式計算該數據,對應計算出的K個hash值

  • 經過hash值找到對應的二進制的陣列下標

  • 如果存在其中一處位置的二進制數據是0,那麽該數據不存在。若是都是1,該數據存在集合中(但由於存在Hash碰撞,判斷數據存在時可能存在誤判)。

  • 布隆過濾器的優缺點

    優勢

  • 因為儲存的是二進制數據,因此占用的空間很小;

  • 它的插入和查詢速度是很是快的,時間復雜度是O(K),能夠聯想一下HashMap的過程;

  • 保密性很好,由於自己不儲存任何原始數據,只有二進制數據

  • 缺點

  • 存在誤判

  • 添加數據是經過計算數據的hash值,hash是存在碰撞的,也就是說,存在兩個不一樣的數據計算獲得相同的hash值。

    例如圖中的你好和hello,假如最終算出hash值相同,那麽他們會將同一個下標的二進制數據改成1。因此也無法確定key為你好和hello是否存在。

  • 刪除困難

  • 如上,你好和hello的hash值相同,對應的陣列下標也是同樣的。如果想刪除你好,即將座標值改為0,可能會影響到其他key,比如是否會連hello都一塊兒刪了之類的。

    面試題2:說一下緩存擊穿吧,你們是怎麽解決的?

    緩存擊穿:指緩存中沒有但資料庫中有的數據(一般是熱點數據緩存時間到期),這時由於並行使用者特別多,同時讀緩存沒讀到數據,又同時去資料庫去查,引起資料庫壓力瞬間增大,線上系統卡住。

    解決方案:

    1、加互斥鎖(mutex key)。 在並行的多個請求中,只有第一個請求執行緒能拿到鎖並執行資料庫查詢操作,其他的執行緒拿不到鎖就阻塞等著,等到第一個執行緒將數據寫入緩存後,直接走緩存。

    互斥鎖

    緩存擊穿後,多個執行緒會同時去查詢資料庫的這條數據,那麽我們可以在第一個查詢數據的請求上使用一個互斥鎖來鎖住它。

    其他的執行緒走到這一步拿不到鎖就等著,等第一個執行緒查詢到了數據,然後做緩存。後面的執行緒進來發現已經有緩存了,就直接走緩存。

    static Lock reenLock = new ReentrantLock();
    public List<String> getData04() throws InterruptedException {
    List<String> result = new ArrayList<String>();
    // 從緩存讀取數據
    result = getDataFromCache();
    if (result.isEmpty()) {
    if (reenLock.tryLock()) {
    try {
    System.out.println("拿到鎖了,從DB獲取資料庫後寫入緩存");
    // 從資料庫查詢數據
    result = getDataFromDB();
    // 將查詢到的數據寫入緩存
    setDataToCache(result);
    finally {
    reenLock.unlock();// 釋放鎖
    }
    else {
    result = getDataFromCache();// 先查一下緩存
    if (result.isEmpty()) {
    System.out.println("我沒拿到鎖,緩存也沒數據,先小憩一下");
    Thread.sleep(100);// 小憩一會兒
    return getData04();// 重試
    }
    }
    }
    return result;
    }

    2、熱點數據不過期。 根據實際業務情況,在Redis中維護一個熱點數據表,批次設為永不過期(如top1000),並定時更新top1000數據。

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

    面試題3:那緩存雪崩說說你們是怎麽解決的?

    緩存雪崩:大量的熱點 key 設定了相同的過期時間,導致緩存在同一時刻全部失效,造成瞬時資料庫請求量大、壓力驟增,引起雪崩,甚至導致資料庫被打掛。緩存雪崩其實有點像升級版的緩存擊穿,緩存擊穿是一個熱點 key,緩存雪崩是一組熱點 key。

    解決方案:

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

    2、熱點數據不過期: 緩存永不過期,異步更新緩存數據。雖然不會出現雪崩效應,卻無法保證數據的一致性。

    3、加互斥鎖: jvm鎖機制、分布式鎖機制都可以。該方式和緩存擊穿一樣,按 key 維度加鎖,對於同一個 key,只允許一個執行緒去計算,其他執行緒原地阻塞等待第一個執行緒的計算結果,然後直接走緩存即可。