1. 程式人生 > >快取內功心法:快取基礎整理

快取內功心法:快取基礎整理

快取雪崩

快取雪崩是由於原有快取失效(過期),新快取未到期間。所有請求都去查詢資料庫,而對資料庫CPU和記憶體造成巨大壓力,嚴重的會造成資料庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。

解決方法:

  1. 一般併發量不是特別多的時候,使用最多的解決方案是加鎖排隊。

  2. 給每一個快取資料增加相應的快取標記,記錄快取的是否失效,如果快取標記失效,則更新資料快取。

    • 快取標記:記錄快取資料是否過期,如果過期會觸發通知另外的執行緒在後臺去更新實際key的快取。
    • 快取資料:它的過期時間比快取標記的時間延長1倍,例:標記快取時間30分鐘,資料快取設定為60分鐘。 這樣,當快取標記key過期後,實際快取還能把舊資料返回給呼叫端,直到另外的執行緒在後臺更新完成後,才會返回新快取。

加鎖排隊方案虛擬碼:

//虛擬碼
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查詢資料
                cacheValue = GetProductListFromDB();
                CacheHelper.Add(cacheKey, cacheValue, cacheTime);
            }
        }
        return cacheValue;
    }
}

快取標記方案虛擬碼:

//虛擬碼
public object GetProductListNew() {
    int cacheTime = 30;
    String cacheKey = "product_list";
    //快取標記
    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查詢資料
            cacheValue = GetProductListFromDB();
            //日期設快取時間的2倍,用於髒讀
            CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);
        });
        return cacheValue;
    }
}

快取穿透

快取穿透是指使用者查詢資料,在資料庫沒有,自然在快取中也不會有。這樣就導致使用者查詢的時候,在快取中找不到,每次都要去資料庫再查詢一遍,然後返回空(相當於進行了兩次無用的查詢)。這樣請求就繞過快取直接查資料庫,這也是經常提的快取命中率問題。

解決方案:

  1. 布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被這個bitmap攔截掉,從而避免了對底層儲存系統的查詢壓力。
  2. 如果一個查詢返回的資料為空(不管是資料不存在,還是系統故障),我們仍然把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘。通過這個直接設定的預設值存放到快取,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問資料庫,這種辦法最簡單粗暴!

方案二虛擬碼:

//虛擬碼
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 {
        //資料庫查詢不到,為空
        cacheValue = GetProductListFromDB();
        if (cacheValue == null) {
            //如果發現為空,設定個預設值,也快取起來
            cacheValue = string.Empty;
        }
        CacheHelper.Add(cacheKey, cacheValue, cacheTime);
        return cacheValue;
    }
}

快取預熱

快取預熱這個應該是一個比較常見的概念,相信很多小夥伴都應該可以很容易的理解,快取預熱就是系統上線後,將相關的快取資料直接載入到快取系統。這樣就可以避免在使用者請求的時候,先查詢資料庫,然後再將資料快取的問題!使用者直接查詢事先被預熱的快取資料!

解決思路:

  1. 直接寫個快取重新整理頁面,上線時手工操作下;
  2. 資料量不大,可以在專案啟動的時候自動進行載入;
  3. 定時重新整理快取;

快取更新

除了快取伺服器自帶的快取失效策略之外,我們還可以根據具體的業務需求進行自定義的快取淘汰,常見的策略有兩種:

  1. 定時去清理過期的快取。
  2. 當有使用者請求過來時,再判斷這個請求所用到的快取是否過期,過期的話就去底層系統得到新資料並更新快取。

快取降級

當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料進行自動降級,也可以配置開關實現人工降級。

降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的。

在進行降級之前要對系統進行梳理,看看系統是不是可以丟卒保帥;從而梳理出哪些必須誓死保護,哪些可降級;比如可以參考日誌級別設定預案:

(1)一般:比如有些服務偶爾因為網路抖動或者服務正在上線而超時,可以自動降級;

(2)警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,併發送告警;

(3)錯誤:比如可用率低於90%,或者資料庫連線池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;

(4)嚴重錯誤:比如因為特殊原因資料錯誤了,此時需要緊急人工降級。

本人免費整理了Java高階資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分散式等教程,一共30G,需要自己領取。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q