高併發緩存架構——雪崩解決方案 (網易雲課堂學習筆記)

1. 高併發緩存架構——雪崩解決方案

  • 網易雲課堂課程地址
  • https://study.163.com/course/courseLearn.htm?courseId=1006355036#/learn/live?lessonId=1053884737&courseId=1006355036

Tip

  • 讀多寫少用緩存,寫多讀少用隊列。

性能(爲什麼要用緩存)

1. MySQL

  • MySQL官方測試報告的機器性能比較完美,實際可以看阿里雲性能白皮書MySQL版
  • https://help.aliyun.com/document_detail/53638.html?spm=a2c4g.11186623.6.1333.5c896fddmFtkF6
  • 常用的8核16G、32G,TPS在1k左右,QPS在2w左右(TPS:是TransactionsPerSecond的縮寫,也就是事務數/秒;QPS:Queries Per Second意思是“每秒查詢率”。)

2. Redis

  • Redis 性能官方文檔
  • https://redis.io/topics/benchmarks
  • Intel® Xeon® CPU E5520 @ 2.27GHz (without pipelining) 單核 12w/s

緩存失效的兩種場景(12306餘票查詢爲例)

  • 高峯期大面積緩存key失效(所有車次查詢全部依賴數據庫)
    • 解決方法簡單,對不同車次設置不同的緩存失效時間不同
  • 局部高峯期,熱點緩存key失效(某一趟車次的海量請求直擊數據庫)

緩存雪崩

  • 因爲緩存服務掛掉或者熱點緩存失效,所有請求都去查數據庫,導致數據庫連接不夠或者數據庫處理不過來,從而導致整個系統不可用。

解決方案

// Java中代碼實現的鎖
Lock lock = new ReentrantLock();

// 查詢方法中,緩存失效時
lock.lock();
try {
    // 再次判斷緩存是否存在
    // 查詢數據庫,重建緩存
} finally {
   lock.unlock(); 
}
  • 優點:簡單有效、適用範圍廣
  • 缺點:阻塞其他線程,鎖的顆粒度太粗

細粒度的鎖

// 每個車次一個鎖
ConcurrentHashMap<String, String> maplock = new ConcurrentHashMap<>();

// 查詢方法中,緩存失效時
boolean lock = false;
try {
    lock = maplock.putIfAbsent(ticketSeq, "true") == null;
    if(lock) {
        // 再次判斷緩存是否存在
        // 查詢數據庫,重建緩存
    } else {
        // 沒拿到鎖的怎麼辦?——緩存降級,根據業務需要降級
        // 方案一,返回固定值
        // 方案二,讀備份緩存(操作主緩存時雙寫,備份緩存不設置過期時間)
        
    }
} finally {
    if(lock) {
        maplock.remove(tiketSeq); 
    }
}

降級策略

  • 優點:靈活多變,方便使用
  • 缺點:需要開發人員掌控業務,增加維護複雜度

多線程單元測試

private static final int THREAD_NUM = 1000;
private CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);

@Test
public void test() throws InterruptedException {
    Thread[] threads = new Thread[THREAD_NUM];
    for(int i=0; i<THREAD_NUM; i++) {
        Thread thread = new Thread(() -> {
            try {
                countDownLatch.await();
                ticketService.queryTicketStock("G290");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        threads[i] = thread;
        thread.start();
        // 倒計時計數器減1,代表又一個線程就緒了
        countDownLatch.countDown();
    }
    // 等待上面所有線程執行完畢後,結束測試
    for (Thread thread : threads) {
        thread.join();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章