Spring緩存(二):解決緩存雪崩、擊穿、穿透問題

1. 緩存穿透問題與解決方案

1.1 什么是緩存穿透

緩存穿透是指查詢一個不存在的數據,由于緩存中沒有這個數據,每次請求都會直接打到數據庫。
如果有惡意用戶不斷請求不存在的數據,就會給數據庫帶來巨大壓力。
這種情況下,緩存失去了保護數據庫的作用。

典型場景:

  • 用戶查詢一個不存在的商品ID
  • 惡意攻擊者故意查詢大量無效數據
  • 業務邏輯錯誤導致的無效查詢

1.2 布隆過濾器解決方案

布隆過濾器是解決緩存穿透最有效的方案之一。它可以快速判斷數據是否可能存在。

@Service
public class ProductService {@Autowiredprivate BloomFilter<String> productBloomFilter;@Autowiredprivate ProductRepository productRepository;@Cacheable(cacheNames = "productCache", key = "#productId", condition = "@productService.mightExist(#productId)")public Product getProduct(String productId) {// 只有布隆過濾器認為可能存在的數據才會查詢數據庫return productRepository.findById(productId).orElse(null);}public boolean mightExist(String productId) {// 布隆過濾器快速判斷,如果返回false則一定不存在return productBloomFilter.mightContain(productId);}@CachePut(cacheNames = "productCache", key = "#product.id")public Product saveProduct(Product product) {// 保存商品時同步更新布隆過濾器Product savedProduct = productRepository.save(product);productBloomFilter.put(product.getId());return savedProduct;}
}

1.3 空值緩存策略

對于確實不存在的數據,我們可以緩存一個空值,避免重復查詢數據庫。

@Service
public class UserService {private static final String NULL_VALUE = "NULL";@Cacheable(cacheNames = "userCache", key = "#userId")public User getUserById(String userId) {User user = userRepository.findById(userId).orElse(null);// 如果用戶不存在,返回一個特殊標記而不是nullreturn user != null ? user : createNullUser();}private User createNullUser() {User nullUser = new User();nullUser.setId(NULL_VALUE);return nullUser;}// 在業務層判斷是否為空值緩存public User getValidUser(String userId) {User user = getUserById(userId);return NULL_VALUE.equals(user.getId()) ? null : user;}
}

2. 緩存擊穿問題與解決方案

2.1 緩存擊穿現象分析

緩存擊穿是指熱點數據的緩存過期時,大量并發請求同時訪問這個數據。
由于緩存中沒有數據,所有請求都會打到數據庫,可能導致數據庫瞬間壓力過大。

常見場景:

  • 熱門商品詳情頁面
  • 明星用戶信息
  • 熱點新聞內容

2.2 互斥鎖解決方案

使用分布式鎖確保只有一個線程去重建緩存,其他線程等待。

@Service
public class HotDataService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate RedissonClient redissonClient;public Product getHotProduct(String productId) {String cacheKey = "hot_product:" + productId;// 先嘗試從緩存獲取Product product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {return product;}// 緩存未命中,使用分布式鎖String lockKey = "lock:product:" + productId;RLock lock = redissonClient.getLock(lockKey);try {// 嘗試獲取鎖,最多等待10秒,鎖30秒后自動釋放if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {// 雙重檢查,防止重復查詢product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {return product;}// 查詢數據庫并更新緩存product = productRepository.findById(productId).orElse(null);if (product != null) {// 設置隨機過期時間,防止緩存雪崩int expireTime = 3600 + new Random().nextInt(600); // 1小時+隨機10分鐘redisTemplate.opsForValue().set(cacheKey, product, expireTime, TimeUnit.SECONDS);}return product;}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}// 獲取鎖失敗,返回空或默認值return null;}
}

2.3 邏輯過期解決方案

設置邏輯過期時間,緩存永不過期,通過后臺線程異步更新。

@Component
public class LogicalExpireCache {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate ThreadPoolExecutor cacheRebuildExecutor;public Product getProductWithLogicalExpire(String productId) {String cacheKey = "logical_product:" + productId;// 獲取緩存數據(包含邏輯過期時間)CacheData<Product> cacheData = (CacheData<Product>) redisTemplate.opsForValue().get(cacheKey);if (cacheData == null) {// 緩存不存在,同步查詢并設置緩存return rebuildCacheSync(productId, cacheKey);}// 檢查邏輯過期時間if (cacheData.getExpireTime().isAfter(LocalDateTime.now())) {// 未過期,直接返回return cacheData.getData();}// 已過期,異步更新緩存,先返回舊數據cacheRebuildExecutor.submit(() -> rebuildCacheAsync(productId, cacheKey));return cacheData.getData();}private Product rebuildCacheSync(String productId, String cacheKey) {Product product = productRepository.findById(productId).orElse(null);if (product != null) {CacheData<Product> cacheData = new CacheData<>();cacheData.setData(product);cacheData.setExpireTime(LocalDateTime.now().plusHours(1)); // 1小時后邏輯過期redisTemplate.opsForValue().set(cacheKey, cacheData);}return product;}private void rebuildCacheAsync(String productId, String cacheKey) {try {rebuildCacheSync(productId, cacheKey);} catch (Exception e) {log.error("異步重建緩存失敗: productId={}", productId, e);}}@Datapublic static class CacheData<T> {private T data;private LocalDateTime expireTime;}
}

3. 緩存雪崩問題與解決方案

3.1 緩存雪崩場景分析

緩存雪崩是指大量緩存在同一時間過期,導致大量請求直接打到數據庫。
這種情況通常發生在系統重啟后或者緩存集中過期時。

典型場景:

  • 系統重啟后緩存全部失效
  • 定時任務統一設置的過期時間
  • Redis服務器宕機

3.2 隨機過期時間策略

通過設置隨機過期時間,避免緩存同時失效。

@Service
public class AntiAvalancheService {@Cacheable(cacheNames = "randomExpireCache", key = "#key")public Object getCacheWithRandomExpire(String key) {// Spring緩存注解本身不支持隨機過期,需要結合Redis操作return dataRepository.findByKey(key);}@CachePut(cacheNames = "randomExpireCache", key = "#key")public Object updateCacheWithRandomExpire(String key, Object data) {// 手動設置隨機過期時間String cacheKey = "randomExpireCache::" + key;int baseExpire = 3600; // 基礎過期時間1小時int randomExpire = new Random().nextInt(1800); // 隨機0-30分鐘redisTemplate.opsForValue().set(cacheKey, data, baseExpire + randomExpire, TimeUnit.SECONDS);return data;}
}

3.3 多級緩存架構

建立多級緩存體系,即使一級緩存失效,還有二級緩存保護。

@Service
public class MultiLevelCacheService {@Autowiredprivate CacheManager l1CacheManager; // 本地緩存@Autowiredprivate RedisTemplate<String, Object> redisTemplate; // Redis緩存public Product getProductMultiLevel(String productId) {// 一級緩存:本地緩存(Caffeine)Cache l1Cache = l1CacheManager.getCache("productL1Cache");Product product = l1Cache.get(productId, Product.class);if (product != null) {return product;}// 二級緩存:Redis緩存String redisKey = "product:" + productId;product = (Product) redisTemplate.opsForValue().get(redisKey);if (product != null) {// 回寫一級緩存l1Cache.put(productId, product);return product;}// 三級:數據庫查詢product = productRepository.findById(productId).orElse(null);if (product != null) {// 同時更新兩級緩存l1Cache.put(productId, product);redisTemplate.opsForValue().set(redisKey, product, Duration.ofHours(2)); // Redis緩存2小時}return product;}@CacheEvict(cacheNames = "productL1Cache", key = "#productId")public void evictProduct(String productId) {// 同時清除Redis緩存redisTemplate.delete("product:" + productId);}
}

4. 電商系統實戰案例

4.1 商品詳情頁緩存策略

電商系統的商品詳情頁是典型的高并發場景,需要綜合應用多種緩存策略。

@Service
public class ProductDetailService {@Autowiredprivate BloomFilter<String> productBloomFilter;@Autowiredprivate RedissonClient redissonClient;// 防穿透 + 防擊穿的商品詳情查詢public ProductDetail getProductDetail(String productId) {// 1. 布隆過濾器防穿透if (!productBloomFilter.mightContain(productId)) {return null; // 商品不存在}String cacheKey = "product_detail:" + productId;// 2. 嘗試從緩存獲取ProductDetail detail = (ProductDetail) redisTemplate.opsForValue().get(cacheKey);if (detail != null) {return detail;}// 3. 緩存未命中,使用分布式鎖防擊穿String lockKey = "lock:product_detail:" + productId;RLock lock = redissonClient.getLock(lockKey);try {if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {// 雙重檢查detail = (ProductDetail) redisTemplate.opsForValue().get(cacheKey);if (detail != null) {return detail;}// 查詢數據庫detail = buildProductDetail(productId);if (detail != null) {// 4. 設置隨機過期時間防雪崩int expireTime = 7200 + new Random().nextInt(3600); // 2-3小時redisTemplate.opsForValue().set(cacheKey, detail, expireTime, TimeUnit.SECONDS);}return detail;}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}return null;}private ProductDetail buildProductDetail(String productId) {// 組裝商品詳情信息Product product = productRepository.findById(productId).orElse(null);if (product == null) {return null;}ProductDetail detail = new ProductDetail();detail.setProduct(product);detail.setInventory(inventoryService.getInventory(productId));detail.setReviews(reviewService.getTopReviews(productId));detail.setRecommendations(recommendationService.getRecommendations(productId));return detail;}
}

4.2 用戶會話緩存管理

用戶會話信息需要考慮安全性和性能,采用分層緩存策略。

@Service
public class UserSessionService {// 敏感信息使用短期緩存@Cacheable(cacheNames = "userSessionCache", key = "#sessionId", condition = "#sessionId != null")public UserSession getUserSession(String sessionId) {return sessionRepository.findBySessionId(sessionId);}// 用戶基礎信息使用長期緩存@Cacheable(cacheNames = "userBasicCache", key = "#userId")public UserBasicInfo getUserBasicInfo(String userId) {return userRepository.findBasicInfoById(userId);}@CacheEvict(cacheNames = {"userSessionCache", "userBasicCache"}, key = "#userId")public void invalidateUserCache(String userId) {// 用戶登出或信息變更時清除相關緩存log.info("清除用戶緩存: {}", userId);}// 防止會話固定攻擊的緩存更新@CachePut(cacheNames = "userSessionCache", key = "#newSessionId")@CacheEvict(cacheNames = "userSessionCache", key = "#oldSessionId")public UserSession refreshSession(String oldSessionId, String newSessionId, String userId) {// 生成新的會話信息UserSession newSession = new UserSession();newSession.setSessionId(newSessionId);newSession.setUserId(userId);newSession.setCreateTime(LocalDateTime.now());sessionRepository.save(newSession);sessionRepository.deleteBySessionId(oldSessionId);return newSession;}
}

5. 緩存監控與告警

5.1 緩存命中率監控

監控緩存的命中率,及時發現緩存問題。

@Component
public class CacheMetricsCollector {private final MeterRegistry meterRegistry;private final Counter cacheHitCounter;private final Counter cacheMissCounter;public CacheMetricsCollector(MeterRegistry meterRegistry) {this.meterRegistry = meterRegistry;this.cacheHitCounter = Counter.builder("cache.hit").description("Cache hit count").register(meterRegistry);this.cacheMissCounter = Counter.builder("cache.miss").description("Cache miss count").register(meterRegistry);}@EventListenerpublic void handleCacheHitEvent(CacheHitEvent event) {cacheHitCounter.increment(Tags.of("cache.name", event.getCacheName()));}@EventListenerpublic void handleCacheMissEvent(CacheMissEvent event) {cacheMissCounter.increment(Tags.of("cache.name", event.getCacheName()));}// 計算緩存命中率public double getCacheHitRate(String cacheName) {double hits = cacheHitCounter.count();double misses = cacheMissCounter.count();return hits / (hits + misses);}
}

5.2 緩存異常告警

當緩存出現異常時,及時告警并降級處理。

@Component
public class CacheExceptionHandler {@EventListenerpublic void handleCacheException(CacheErrorEvent event) {log.error("緩存異常: cache={}, key={}, exception={}", event.getCacheName(), event.getKey(), event.getException().getMessage());// 發送告警alertService.sendAlert("緩存異常", String.format("緩存 %s 發生異常: %s", event.getCacheName(), event.getException().getMessage()));// 記錄異常指標meterRegistry.counter("cache.error", "cache.name", event.getCacheName()).increment();}// 緩存降級處理@Recoverpublic Object recoverFromCacheException(Exception ex, String key) {log.warn("緩存操作失敗,執行降級邏輯: key={}", key);// 直接查詢數據庫或返回默認值return fallbackDataService.getFallbackData(key);}
}

6. 最佳實踐總結

6.1 緩存策略選擇指南

緩存穿透解決方案選擇:

  • 數據量大且查詢模式固定:使用布隆過濾器
  • 數據量小且查詢隨機性強:使用空值緩存
  • 對一致性要求高:布隆過濾器 + 空值緩存組合

緩存擊穿解決方案選擇:

  • 對實時性要求高:使用互斥鎖方案
  • 對可用性要求高:使用邏輯過期方案
  • 并發量特別大:邏輯過期 + 異步更新

緩存雪崩解決方案選擇:

  • 單機應用:隨機過期時間 + 本地緩存
  • 分布式應用:多級緩存 + 熔斷降級
  • 高可用要求:Redis集群 + 多級緩存

6.2 性能優化建議

  1. 合理設置過期時間:根據數據更新頻率設置,避免過長或過短
  2. 控制緩存大小:定期清理無用緩存,避免內存溢出
  3. 監控緩存指標:關注命中率、響應時間、錯誤率等關鍵指標
  4. 預熱關鍵緩存:系統啟動時預加載熱點數據
  5. 異步更新策略:對于非關鍵數據,采用異步更新減少響應時間

通過合理應用這些緩存策略,可以有效提升系統性能,保障服務穩定性。
記住,緩存是把雙刃劍,既要享受性能提升,也要處理好數據一致性問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/922101.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/922101.shtml
英文地址,請注明出處:http://en.pswp.cn/news/922101.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

PHP 與 WebAssembly 的 “天然隔閡”

WebAssembly&#xff08;簡稱 WASM&#xff09;是一種低級二進制指令格式&#xff0c;旨在為高級語言提供高性能的編譯目標&#xff0c;尤其在瀏覽器環境中實現接近原生的執行效率。它主要用于前端性能密集型場景&#xff08;如游戲引擎、視頻編解碼、3D 渲染等&#xff09;&am…

unity中通過拖拽,自定義scroll view中子物體順序

1.在每個content的子物體上掛載DragHandler腳本&#xff0c;并且添加Canvs Group組件&#xff0c;設置見圖2.DragHandler腳本內容&#xff1a;using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using System.Collections.Generic; using System.Coll…

用 Matplotlib 繪制餅圖:從基礎語法到實戰美化,全面掌握分類數據可視化技巧

用 Matplotlib 繪制餅圖:從基礎語法到實戰美化,全面掌握分類數據可視化技巧 在數據分析與可視化的世界里,**“圖勝千言”**早已成為共識。而在眾多圖表類型中,餅圖(Pie Chart)以其直觀的比例展示方式,成為展示分類數據分布的常見選擇。無論是業務報表、用戶畫像,還是市…

基礎算法之二分算法 --- 2

大家好&#xff0c;不同的時間&#xff0c;相同的地點&#xff0c;時隔多日我們又見面了。繼上次的二分算法后&#xff0c;我們這次要來學習的是二分答案了。這個部分相較于前面的二分算法難度有相當的提升&#xff0c;希望大家有所準備。雖然難度增加了&#xff0c;但是博主還…

發揮nano banana的最大能力

1. 概述Nano Banana 簡介&#xff1a;Nano Banana 是 Google DeepMind 開發的 AI 圖像生成與編輯模型&#xff0c;集成在 Google Gemini 平臺中&#xff08;具體為 Gemini 2.5 Flash 版本&#xff09;。它以高效的圖像編輯能力聞名&#xff0c;尤其在角色一致性、光影理解和快速…

leetcode 面試題01.02判定是否互為字符重排

一、問題描述二、解題思路解法一&#xff1a;對s1和s2進行sort排序&#xff0c;返回s1是否等于s2&#xff1b;解法二&#xff1a;用哈希表分別來記錄s1和s2中字符出現的次數&#xff0c;統計完后&#xff0c;判斷兩個哈希表是否相等;三、代碼實現解法一&#xff1a;時間復雜度&…

Python Yolo8 物體識別

支持單張圖片/圖片目錄批量預標注 默認使用cuda GPU .env HTTP_PROXYhttp://192.168.2.109:10808 HTTPS_PROXYhttp://192.168.2.109:10808pyproject.toml [project] name "yolo-test" version "0.1.0" description "Add your description here&quo…

LeetCode100-234回文鏈表

本文基于各個大佬的文章上點關注下點贊&#xff0c;明天一定更燦爛&#xff01;前言Python基礎好像會了又好像沒會&#xff0c;所有我直接開始刷leetcode一邊抄樣例代碼一邊學習吧。本系列文章用來記錄學習中的思考&#xff0c;寫給自己看的&#xff0c;也歡迎大家在評論區指導…

BUG排查流程

引言簡述Bug排查的重要性分享個人或團隊在Bug排查中的常見挑戰引出日記形式記錄的價值日記格式設計時間戳&#xff1a;記錄問題發現和解決的時間節點問題描述&#xff1a;清晰定義Bug的現象和影響范圍環境信息&#xff1a;操作系統、版本號、依賴庫等關鍵配置復現步驟&#xff…

汽車功能安全 Functional Safety ISO 26262 測試之一

汽車電子電氣系統的日益復雜使得功能安全成為保障車輛可靠性和駕乘安全的關鍵。 本文將圍繞ISO 26262標準的核心內容展開&#xff0c;幫助大家理解如何通過系統化的方法控制風險&#xff0c;進行測試&#xff0c;確保產品安全。 01 什么是功能安全&#xff1f; 首先&#xff0c…

人形機器人賽道的隱形勝負手:低延遲視頻鏈路如何決定機器人未來

一、引言&#xff1a;爆發前夜的人形機器人賽道 2025 年&#xff0c;被業內稱為“人形機器人量產元年”。政策與資本的合力&#xff0c;讓這條原本還帶著科幻色彩的產業賽道&#xff0c;驟然進入現實加速期。國家層面&#xff0c;《“機器人”行動計劃》明確提出要推動人形機器…

從iPhone 17取消SIM卡槽,看企業如何告別“數據孤島”

9月10日&#xff0c;蘋果公司如期召開秋季新品發布會&#xff0c;正式推出iPhone 17系列。除了性能和拍照的常規升級&#xff0c;一個看似不起眼但意義深遠的改變引起了廣泛關注——iPhone 17 Pro系列全面取消了實體SIM卡槽&#xff0c;只保留了eSIM功能。這一舉動不僅僅是技術…

【JavaWeb01】Web介紹

文章目錄1.導學2.Web開發介紹2.1 Web網站的工作流程2.2 前后端分離開發1.導學 2.Web開發介紹 2.1 Web網站的工作流程 瀏覽器根據請求的域名請求對應的前端服務器&#xff0c;前端服務器接收到請求之后&#xff0c;把對應的前端代碼返回給服務器。瀏覽器中有解析前端代碼的解析引…

鏈路預測算法MATLAB實現

鏈路預測算法MATLAB實現 鏈路預測是復雜網絡分析中的重要任務&#xff0c;旨在預測網絡中尚未連接的兩個節點之間未來產生連接的可能性。 程序概述 MATLAB程序實現了以下鏈路預測算法&#xff1a; 基于局部信息的相似性指標&#xff08;Common Neighbors, Jaccard, Adamic-Adar…

淘寶商品詳情 API 的安全強化與生態協同創新路徑

一、安全強化&#xff1a;從 “被動防御” 到 “主動免疫” 的體系升級動態身份認證與權限顆粒化構建 “生物特征 設備指紋 行為基線” 的三重認證機制&#xff1a;結合用戶操作習慣&#xff08;如點擊間隔、滑動軌跡&#xff09;生成動態令牌&#xff0c;對高權限接口&#…

快消26屆聯合利華校招AI測評及第二輪線上認知能力測評SHL筆試真題及評分要求

在求職的道路上&#xff0c;聯合利華作為一家全球知名企業&#xff0c;其招聘流程一直備受關注。尤其是其AI面試環節&#xff0c;更是讓許多求職者既期待又緊張。本文將詳細總結聯合利華AI面試的規律與應對策略&#xff0c;希望能為正在準備面試的你提供一些幫助。一、聯合利華…

使用Langchain生成本地rag知識庫并搭載大模型

準備設備&#xff1a; 手機aidlux2.0個人版 一、下載依賴pip install langchain langchain-community faiss-cpu pypdf二、安裝ollama并下載模型 curl -fsSL https://ollama.com/install.sh | sh #需要科學上網 ollama serve & #讓ollama服務在后臺運行安裝完畢可以查看oll…

L2-【英音】地道語音語調--語調

文章目錄語調英式語調四步法語調含義降調升調降升調升降語調如何正確表情達意1. 用降調的句型語調 英語里沒有任何一句話具有固定節奏模式 英式語調四步法 意群劃分重音核心語調&#xff08;重中之重&#xff09;語調的選擇 A French burglar broke-into-a flat while the o…

計算機視覺進階教學之圖像投影(透視)變換

目錄 簡介 一、了解圖像投影(透視)變換 一、定義與原理 二、應用場景 三、實現方法 二、案例分析 1. 輔助函數定義 1.1.cv_show 函數 1.2.order_points 函數 1.3.four_point_transform 函數 1.4.resize 函數 2. 主程序執行流程 2.1.圖像縮放處理 2.2.輪廓檢測 2.…

Java面試問題記錄(二)

三、系統設計與問題排查1、假設你要設計一個 “秒殺系統”&#xff0c;需要考慮高并發、高可用、防超賣等問題&#xff0c;你的整體技術方案是什么&#xff1f;從前端、接口層、服務層、存儲層分別說說核心設計點。秒殺系統設計設計核心&#xff1a;瞬時高并發&#xff0c;庫存…