緩存穿透、緩存併發和緩存雪崩是常見的由於併發量大而導致的緩存問題。這裏記錄下其產生原因和解決方案。
緩存穿透是由惡意攻擊或無意造成的;緩存併發是由設計不足造成的;緩存雪崩是由緩存同時失效造成的。
一、緩存穿透
概念:
緩存穿透指的是使用不存在的 key 進行大量的高併發查詢,這導致緩存無法命中,每次請求都要穿透到後端數據庫系統進行查詢,使數據庫壓力過大,甚至使數據庫服務被壓死。
解決方案:
1、我們通常將空值緩存起來,再次接收到同樣的查詢請求時,若命中緩存並且值爲空,就會直接返回,不會穿透的數據庫,避免緩存穿透。
2、當然,有時惡意襲擊者可以猜到我們使用了這種方案,每次都會使用不同的參數來查詢,這就需要我們對輸入的參數進行過濾,例如,如果我們使用 ID 進行查詢,則可以對 ID 的格式進行分析,如果不符合產生 ID 的規則,就直接拒絕,或者在 ID 上放入時間信息,根據時間信息判斷 ID 是否合法 ,或者是否是我們曾經生成的 ID,這樣可以攔截一定的無效請求。
二、緩存併發
概念:
緩存併發的問題通常發生在高併發的場景下,當一個緩存 key 過期時,因爲訪問這個緩存 key 的請求量較大,多個請求同時發現緩存過期,因此多個請求會同時訪問數據庫來查詢最新的數據,並且回寫緩存,這樣會造成應用和數據庫的負載增加,性能降低,由於併發較高,甚至會導致數據庫被壓死。
解決方案:
1、分佈式鎖
使用分佈式鎖,保證對於每個 key 同時只有一個線程去查詢後端服務,其他線程沒有獲得分佈式鎖的權限,因此只需要等待即可。這種方式將高併發的壓力轉移到了分佈式鎖,因此對分佈式鎖的考驗很大。
2、本地鎖
與分佈式鎖類似,我們通過本地鎖的方式來限制只有一個線程去數據庫查詢數據,而其他線程只需等待,等前面的線程查詢到數據後再訪問緩存。但是,這種方法只能限制一個服務節點只有一個線程去數據庫中查詢,如果一個服務有多個節點,則還會有多個數據庫查詢操作,也就是說在節點數量較多的情況下並沒有完全解決緩存併發的問題。
3、軟過期
軟過期是指對緩存中的數據設置失效時間,就是不使用緩存服務器提供的過期時間,而是業務層在數據中存儲過期時間信息,由業務程序判斷是否過期並更新,在發現了數據即將過期時,將緩存的時效延長,程序可以派遣一個線程去數據庫中獲取最新的數據,其他線程這時看到延長了的過期時間,就會繼續使用舊數據,等派遣的線程獲取最新數據後再更新緩存。也可以通過異步更新服務來更新設置軟過期的緩存,這樣應用層就不用關心緩存併發的問題了。
三、緩存雪崩
概念:
緩存雪崩指緩存服務器重啓或者大量緩存集中在某一個時間段內失效,給後端數據庫造成瞬間的負載升高的壓力,甚至壓垮數據庫的情況。
解決方案:
通常的解決辦法是對不同的數據使用不同的失效時間,甚至對相同的數據、不同的請求使用不同的失效時間。例如,我們要緩存 user 數據,會對每個用戶的數據設置不同的緩存過期時間,可以定義一個基礎時間,假設 10 秒,然後加上一個兩秒以內的隨機數,過期時間爲 10 ~ 12 秒,就會避免緩存雪崩。