大廠Redis緩存雪崩,穿透,擊穿,降級,預熱等解決方案,面試官想知道的都在這!

你們好,我是清風!以前分享過大廠Redis高併發場景設計,面試問的都在這!互聯網大廠Java工程師面試指南——Redis篇,今天給小夥伴說說大廠面試高頻必問點(緩存穿透,雪崩等問題)!以爲不錯的小夥伴能夠關注點贊一下,感謝支持!java

寫在前面

2020年面試必備的Java後端進階面試題總結了一份複習指南在Github上,內容詳細,圖文並茂,有須要學習的朋友能夠Star一下! GitHub地址: github.com/Java-Ling/J…git

1、緩存雪崩

一、什麼是緩存雪崩?

緩存雪崩是指在咱們設置緩存時採用了相同的過時時間,致使緩存在某一時刻同時失效,請求所有轉發到DB,DB瞬時壓力太重雪崩。因爲原有緩存失效,新緩存未到期間全部本來應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存形成巨大壓力,嚴重的會形成數據庫宕機。github

二、緩存雪崩問題排查

  1. 在一個較短的時間內,緩存中較多的key集中過時
  2. 此週期內請求訪問過時的數據,redis未命中,redis向數據庫獲取數據
  3. 數據庫同時接收到大量的請求沒法及時處理
  4. Redis大量請求被積壓,開始出現超時現象
  5. 數據庫流量激增,數據庫崩潰
  6. 重啓後仍然面對緩存中無數據可用
  7. Redis服務器資源被嚴重佔用,Redis服務器崩潰
  8. Redis集羣呈現崩塌,集羣瓦解
  9. 應用服務器沒法及時獲得數據響應請求,來自客戶端的請求數量愈來愈多,應用服務器崩潰
  10. 應用服務器,redis,數據庫所有重啓,效果不理想

三、有什麼解決方案來防止緩存雪崩?

  1. 更多的頁面靜態化處理
  2. 構建多級緩存架構 Nginx緩存+redis緩存+ehcache緩存
  3. 檢測Mysql嚴重耗時業務進行優化 對數據庫的瓶頸排查:例如超時查詢、耗時較高事務等
  4. 災難預警機制
    • 監控redis服務器性能指標
    • CPU佔用、CPU使用率
    • 內存容量
    • 查詢平均響應時間
    • 線程數
  5. 限流、降級 短期範圍內犧牲一些客戶體驗,限制一部分請求訪問,下降應用服務器壓力,待業務低速運轉後再逐步放開訪問
  6. LRU與LFU切換 2. 數據有效期策略調整
    • 根據業務數據有效期進行分類錯峯,A類90分鐘,B類80分鐘,C類70分鐘
    • 過時時間使用固定時間+隨機值的形式,稀釋集中到期的key的數量
  7. 超熱數據使用永久key
  8. 按期維護(自動+人工) 對即將過時數據作訪問量分析,確認是否延時,配合訪問量統計,作熱點數據的延時 5. 加鎖

4.總結

緩存雪崩就是瞬間過時數據量太大,致使對數據庫服務器形成壓力。如可以有效避免過時時間集中,能夠有效解決雪崩現象的出現(約40%),配合其餘策略一塊兒使用,並監控服務器的運行數據,根據運行記錄作快速調整。面試

2、緩存預熱

1.什麼是緩存預熱

緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。這樣就能夠避免在用戶請求的時候,先查詢數據庫,而後再將數據緩存的問題。用戶直接查詢事先被預熱的緩存數據。如圖所示:redis

若是不進行預熱, 那麼 Redis 初識狀態數據爲空,系統上線初期,對於高併發的流量,都會訪問到數據庫中, 對數據庫形成流量的壓力。sql

2.問題排查

  1. 請求數量較高
  2. 主從之間數據吞吐量較大,數據同步操做頻度較高

3.有什麼解決方案?

前置準備工做:數據庫

  1. 平常例行統計數據訪問記錄,統計訪問頻度較高的熱點數據後端

  2. 利用LRU數據刪除策略,構建數據留存隊列緩存

    例如:storm與kafka配合
    複製代碼

準備工做: 3. 將統計結果中的數據分類,根據級別,redis優先加載級別較高的熱點數據 4. 利用分佈式多服務器同時進行數據讀取,提速數據加載過程 5. 熱點數據主從同時預熱服務器

實施: 6. 使用腳本程序固定觸發數據預熱過程 7. 若是條件容許,使用了CDN(內容分發網絡),效果會更好

4.總結

緩存預熱就是系統啓動前,提早將相關的緩存數據直接加載到緩存系統。避免在用戶請求的時候,先查詢數據庫,而後再將數據緩存的問題!用戶直接查詢事先被預熱的緩存數據

3、緩存穿透

一、什麼是緩存穿透?

緩存穿透是指用戶查詢數據,在數據庫沒有,天然在緩存中也不會有。這樣就致使用戶查詢的時候,在緩存中找不到對應key的value,每次都要去數據庫再查詢一遍,而後返回空(至關於進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫

二、有什麼解決方案來防止緩存穿透?

一、緩存空值

若是一個查詢返回的數據爲空(無論是數據不存在,仍是系統故障)咱們仍然把這個空結果進行緩存,但它的過時時間會很短,最長不超過5分鐘。經過這個設置的默認值存放到緩存,這樣第二次到緩存中獲取就有值了,而不會繼續訪問數據庫

二、採用布隆過濾器BloomFilter

**優點:**佔用內存空間很小,位存儲;性能特別高,使用key的hash判斷key存不存在

將全部可能存在的數據哈希到一個足夠大的bitmap中,一個必定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力

在緩存以前在加一層BloomFilter,在查詢的時候先去BloomFilter去查詢key是否存在,若是不存在就直接返回,存在再去查詢緩存,緩存中沒有再去查詢數據庫

3.總結

緩存擊穿訪問了不存在的數據,跳過了合法數據的redis數據緩存階段,每次訪問數據庫,致使對數據庫服務器形成壓力。一般此類數據的出現量是一個較低的值,當出現此類狀況以毒攻毒,並及時報警。應對策略應該在臨時預案防範方面多作文章。不管是黑名單仍是白名單,都是對總體系統的壓力,警報解除後儘快移除。

4、緩存降級

降級的狀況,就是緩存失效或者緩存服務掛掉的狀況下,咱們也不去訪問數據庫。咱們直接訪問內存部分數據緩存或者直接返回默認數據。

舉例來講:

對於應用的首頁,通常是訪問量很是大的地方,首頁裏面每每包含了部分推薦商品的展現信息。這些推薦商品都會放到緩存中進行存儲,同時咱們爲了不緩存的異常狀況,對熱點商品數據也存儲到了內存中。同時內存中還保留了一些默認的商品信息。以下圖所示:

降級通常是有損的操做,因此儘可能減小降級對於業務的影響程度。

5、緩存擊穿

一、什麼是緩存擊穿?

在日常高併發的系統中,大量的請求同時查詢一個key時,此時這個key正好失效了,就會致使大量的請求都打到數據庫上面去。這種現象咱們稱爲緩存擊穿

二、問題排查

  1. Redis中某個key過時,該key訪問量巨大
  2. 多個數據請求從服務器直接壓到Redis後,均未命中
  3. Redis在短期內發起了大量對數據庫中同一數據的訪問

三、如何解決

1. 使用互斥鎖(mutex key)

這種解決方案思路比較簡單,就是隻讓一個線程構建緩存,其餘線程等待構建緩存的線程執行完,從新從緩存獲取數據就能夠了。若是是單機,能夠用synchronized或者lock來處理,若是是分佈式環境能夠用分佈式鎖就能夠了(分佈式鎖,能夠用memcache的add, redis的setnx, zookeeper的添加節點操做)。

在這裏插入圖片描述

2. "提早"使用互斥鎖(mutex key)

在value內部設置1個超時值(timeout1), timeout1比實際的redis timeout(timeout2)小。當從cache讀取到timeout1發現它已通過期時候,立刻延長timeout1並從新設置到cache。而後再從數據庫加載數據並設置到cache中

3. "永遠不過時"

  • 從redis上看,確實沒有設置過時時間,這就保證了,不會出現熱點key過時問題,也就是「物理」不過時。
  • 從功能上看,若是不過時,那不就成靜態的了嗎?因此咱們把過時時間存在key對應的value裏,若是發現要過時了,經過一個後臺的異步線程進行緩存的構建,也就是「邏輯」過時

4. 緩存屏障

class MyCache{

    private ConcurrentHashMap<String, String> map;

    private CountDownLatch countDownLatch;

    private AtomicInteger atomicInteger;

    public MyCache(ConcurrentHashMap<String, String> map, CountDownLatch countDownLatch, AtomicInteger atomicInteger) {
        this.map = map;
        this.countDownLatch = countDownLatch;
        this.atomicInteger = atomicInteger;
    }

    public String get(String key){

        String value = map.get(key);
        if (value != null){
            System.out.println(Thread.currentThread().getName()+"\t 線程獲取value值 value="+value);
            return value;
        }
        // 若是沒獲取到值
        // 首先嚐試獲取token,而後去查詢db,初始化化緩存;
        // 若是沒有獲取到token,超時等待
        if (atomicInteger.compareAndSet(0,1)){
            System.out.println(Thread.currentThread().getName()+"\t 線程獲取token");
            return null;
        }

        // 其餘線程超時等待
        try {
            System.out.println(Thread.currentThread().getName()+"\t 線程沒有獲取token,等待中。。。");
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 初始化緩存成功,等待線程被喚醒
        // 等待線程等待超時,自動喚醒
        System.out.println(Thread.currentThread().getName()+"\t 線程被喚醒,獲取value ="+map.get("key"));
        return map.get(key);
    }

    public void put(String key, String value){

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        map.put(key, value);

        // 更新狀態
        atomicInteger.compareAndSet(1, 2);

        // 通知其餘線程
        countDownLatch.countDown();
        System.out.println();
        System.out.println(Thread.currentThread().getName()+"\t 線程初始化緩存成功!value ="+map.get("key"));
    }

}

class MyThread implements Runnable{

    private MyCache myCache;

    public MyThread(MyCache myCache) {
        this.myCache = myCache;
    }

    @Override
    public void run() {
        String value = myCache.get("key");
        if (value == null){
            myCache.put("key","value");
        }

    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {

        MyCache myCache = new MyCache(new ConcurrentHashMap<>(), new CountDownLatch(1), new AtomicInteger(0));

        MyThread myThread = new MyThread(myCache);

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executorService.execute(myThread);
        }
    }
}
複製代碼

4.總結

緩存擊穿就是單個高熱數據過時的瞬間,數據訪問量較大,未命中redis後,發起了大量對同一數據的數據庫訪問,致使對數據庫服務器形成壓力。應對策略應該在業務數據分析與預防方面進行,配合運行監控測試與即時調整策略,畢竟單個key的過時監控難度較高,配合雪崩處理策略便可。

6、總結

這些都是實際項目中,可能碰到的一些問題,也是面試的時候常常會被問到的知識點,實際上還有不少不少各類各樣的問題,文中的解決方案,也不可能知足全部的場景,相對來講只是對該問題的入門解決方法。通常正式的業務場景每每要複雜的多,應用場景不一樣,方法和解決方案也不一樣,因爲上述方案,考慮的問題並非很全面,所以並不適用於正式的項目開發,可是能夠做爲概念理解入門,具體解決方案要根據實際狀況來肯定!

相關文章
相關標籤/搜索