
🎈🎈作者主頁: 喔的嘛呀🎈🎈
目錄
引言
1. 緩存雪崩
1.1 問題描述
1.2 解決方案
1.2.1 加鎖防止并發重建緩存
2. 緩存穿透
2.1 問題描述
2.2 解決方案
2.2.1 布隆過濾器防止無效請求
3. 緩存預熱
3.1 問題描述
3.2 分析與解決方案
3.2.1 定時任務預熱緩存
4. 緩存更新
4.1 問題描述
4.2 分析與解決方案
4.2.1 主動更新緩存
5. 緩存降級
5.1 問題描述
5.2 分析與解決方案
5.2.1 降級機制提供默認值
總結
引言
在系統開發中,緩存是提升性能和降低數據庫負載的重要手段。然而,緩存并非沒有問題,常見的問題包括緩存雪崩、緩存穿透、緩存預熱、緩存更新和緩存降級等。本文將詳細分析這些緩存相關的問題,并提供解決方案。
1. 緩存雪崩
1.1 問題描述
? ? ? ?緩存雪崩是指在緩存中的大量數據同時過期或失效,導致大量請求直接落到數據庫,壓力劇增,可能導致系統崩潰。我們可以簡單的理解為:由于原有緩存失效,新緩存未到期間 (例如:我們設置緩存時采用了相同的過期時間,在同一時刻出現大面積的緩存過期),所有原本應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。
1.2 解決方案
1.2.1 加鎖防止并發重建緩存
public class CacheService {private final Object lock = new Object();public Object getData(String key) {Object data = getFromCache(key);if (data == null) {synchronized (lock) {data = getFromCache(key);if (data == null) {data = getFromDatabase(key);putIntoCache(key, data);}}}return data;}// 其他業務邏輯...
}
2. 緩存穿透
2.1 問題描述
? ? ? ? 緩存穿透是指用戶查詢數據,在數據庫沒有,自然在緩存中也不會有。這樣就導致用戶查詢的時候,在緩存中找不到,每次都要去數據庫再查詢一遍,然后返回空(相當于進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫,這也是經常提的緩存命中率問題。
2.2 解決方案
2.2.1 布隆過濾器防止無效請求
public class CacheService {private final BloomFilter<String> bloomFilter = new BloomFilter<>();public Object getData(String key) {if (!bloomFilter.mightContain(key)) {return null;}Object data = getFromCache(key);if (data == null) {data = getFromDatabase(key);putIntoCache(key, data);}return data;}// 其他業務邏輯...
}
3. 緩存預熱
3.1 問題描述
? ? ? 緩存預熱是指在系統上線或重啟后,將部分或全部數據預先加載到緩存中,防止大量請求直接訪問數據庫。
3.2 分析與解決方案
3.2.1 定時任務預熱緩存
通過定時任務,在系統啟動或每天凌晨1點等時機,將需要預熱的數據加載到緩存中:
@Component
public class CacheWarmUpTask {@Autowiredprivate CacheService cacheService;@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1點執行public void warmUpCache() {List<String> keysToWarmUp = getKeysToWarmUp();for (String key : keysToWarmUp) {cacheService.getData(key);}}private List<String> getKeysToWarmUp() {// 根據業務邏輯獲取需要預熱的緩存鍵列表// ...}
}
4. 緩存更新
4.1 問題描述
緩存更新是指數據庫中的數據更新后,及時將緩存中的數據進行同步。
4.2 分析與解決方案
4.2.1 主動更新緩存
? ? ? ? 除了緩存服務器自帶的緩存失效策略之外(Redis默認的有6中策略可供選擇),我們還可以根據具體的業務需求進行自定義的緩存淘汰,常見的策略有兩種: (1)定時去清理過期的緩存; (2)當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據并更新緩存。 兩者各有優劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較復雜!具體用哪種方案,大家可以根據自己的應用場景來權衡。
public class CacheService {public void updateCache(String key, Object newData) {// 更新緩存putIntoCache(key, newData);}// 其他業務邏輯...
}
5. 緩存降級
5.1 問題描述
? ? ? ?緩存降級是指在系統遇到異常或緩存失效的情況下,通過某種方式提供默認值或兜底數據,保證系統正常運行。
? ? ? ?當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵數據進行自動降級,也可以配置開關實現人工降級。 降級的最終目的是保證核心服務可用,即使是有損的而且有些服務是無法降級的(如加入購物車、結算)。 以參考日志級別設置預案: (1)一般:比如有些服務偶爾因為網絡抖動或者服務正在上線而超時,可以自動降級; (2)警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,并發送告警; (3)錯誤:比如可用率低于90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級; (4)嚴重錯誤:比如因為特殊原因數據錯誤了,此時需要緊急人工降級。服務降級的目的,是為了防止Redis服務故障,導致數據庫跟著一起發生雪崩問題。因此,對于不重要的緩存數據,可以采取服務降級策略,例如一個比較常見的做法就是Redis出現問題,不去數據庫查詢,而是直接返回默認值給用戶。
5.2 分析與解決方案
5.2.1 降級機制提供默認值
在緩存失效或異常時,提供默認值或兜底數據,確保系統正常運行:
public class CacheService {public Object getData(String key) {Object data = getFromCache(key);if (data == null) {data = getFromDatabase(key);if (data != null) {putIntoCache(key, data);} else {data = getDefaultData();}}return data;}private Object getDefaultData() {// 提供默認值或兜底數據// ...}// 其他業務邏輯...
}
總結
? ? ? ?通過深入分析緩存雪崩、緩存穿透、緩存預熱、緩存更新、緩存降級等問題,并提供相應的解決方案,可以有效提高系統的穩定性和性能。在實際應用中,應根據業務場景選擇合適的方案,綜合考慮多方面因素,以保障系統的高可用性和穩定性。長文分析力求全面,希望能為讀者提供深度的理解和實踐指導。