緩存三大劫攻防戰:穿透、擊穿、雪崩的Java實戰防御體系(二)

第二部分:緩存擊穿——熱點key過期引發的“DB瞬間高壓”

緩存擊穿的本質是“某個熱點key(高并發訪問)突然過期”,導致大量請求在同一時間穿透緩存,集中沖擊DB,形成“瞬間高壓”。

案例3:電商秒殺的“庫存超賣”驚魂

故障現場

某電商平臺“618”秒殺活動中,一款限量1000臺的手機采用“Redis緩存+MySQL”架構:

  • 緩存key:seckill:stock:1001(存儲庫存數量),過期時間1小時;
  • 流程:查詢緩存→未命中則查DB→扣減庫存→更新緩存。
  • 故障:活動開始1小時后,緩存key恰好過期,此時2000+用戶同時刷新頁面,緩存未命中,所有請求直達MySQL查詢庫存。MySQL因瞬間高并發(2000QPS)出現鎖等待,庫存更新延遲,最終超賣50臺。
根因解剖
  1. 熱點key(seckill:stock:1001)過期瞬間,2000+并發請求穿透至MySQL;
  2. MySQL查詢庫存時加行鎖(SELECT stock FROM seckill WHERE item_id=1001 FOR UPDATE),并發請求排隊等待,導致庫存更新延遲;
  3. 前端未做防重放處理,用戶多次刷新加劇并發。
三重防御方案落地
方案1:熱點數據“邏輯永不過期”

核心邏輯:緩存不設置物理過期時間,而是在value中嵌入“邏輯過期時間”。當邏輯過期時,不直接刪除緩存,而是通過后臺線程異步更新,當前請求仍返回舊數據。
優勢:徹底避免過期瞬間的并發穿透。

實戰代碼

@Service
public class SeckillStockService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate SeckillMapper seckillMapper;// 線程池:處理緩存異步更新private final ExecutorService updatePool = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new ThreadPoolExecutor.CallerRunsPolicy());// 緩存數據模型(含邏輯過期時間)@Datastatic class StockCache {private Integer stock; // 庫存數量private long expireTime; // 邏輯過期時間(毫秒)}/*** 查詢秒殺庫存(邏輯永不過期)*/public Integer getStock(Long itemId) {String cacheKey = "seckill:stock:" + itemId;// 1. 查詢緩存String cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal == null) {// 2. 緩存未命中(首次加載):加鎖查詢DB并初始化return loadStockWithLock(itemId, cacheKey);}// 3. 解析緩存數據StockCache cache = JSON.parseObject(cacheVal, StockCache.class);// 4. 邏輯未過期:直接返回if (System.currentTimeMillis() < cache.getExpireTime()) {return cache.getStock();}// 5. 邏輯已過期:異步更新緩存,當前請求返回舊數據updatePool.submit(() -> refreshStockCache(itemId, cacheKey));return cache.getStock();}// 加鎖加載庫存(防止緩存擊穿)private Integer loadStockWithLock(Long itemId, String cacheKey) {// 使用Redisson分布式鎖RLock lock = redissonClient.getLock("lock:seckill:stock:" + itemId);try {// 最多等待100ms,持有鎖5秒if (lock.tryLock(100, 5000, TimeUnit.MILLISECONDS)) {// 雙重檢查:防止重復加載String cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal != null) {return JSON.parseObject(cacheVal, StockCache.class).getStock();}// 查詢DB并初始化緩存(邏輯過期1小時)Integer stock = seckillMapper.selectStock(itemId);StockCache cache = new StockCache();cache.setStock(stock);cache.setExpireTime(System.currentTimeMillis() + 3600 * 1000);redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(cache));return stock;} else {// 獲取鎖失敗:返回DB查詢結果(兜底)return seckillMapper.selectStock(itemId);}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}// 刷新緩存(異步執行)private void refreshStockCache(Long itemId, String cacheKey) {RLock lock = redissonClient.getLock("lock:seckill:stock:" + itemId);try {// 加鎖防止并發更新if (lock.tryLock(100, 5000, TimeUnit.MILLISECONDS)) {Integer newStock = seckillMapper.selectStock(itemId);StockCache cache = new StockCache();cache.setStock(newStock);cache.setExpireTime(System.currentTimeMillis() + 3600 * 1000);redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(cache));}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
}

時序圖

正常請求(未過期):
[用戶] → 查緩存 → 命中(未過期)→ 返回結果過期請求(異步更新):
[用戶] → 查緩存 → 命中(已過期)→ 返回舊數據↓異步線程更新緩存(加鎖)

實戰效果:緩存過期時無請求穿透至DB,MySQL查詢量穩定在50QPS以內,超賣問題徹底解決。

方案2:分布式鎖“串行化”查詢

核心邏輯:熱點key過期時,通過分布式鎖保證只有一個線程能查詢DB并更新緩存,其他線程等待重試。
適用場景:數據實時性要求高,無法接受舊數據。

實戰代碼(Redisson實現)

@Service
public class HotItemService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate ItemMapper itemMapper;/*** 查詢熱點商品詳情(分布式鎖防擊穿)*/public ItemDTO getHotItem(Long itemId) {String cacheKey = "item:hot:" + itemId;// 1. 查詢緩存String cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal != null) {return JSON.parseObject(cacheVal, ItemDTO.class);}// 2. 緩存未命中:加分布式鎖RLock lock = redissonClient.getLock("lock:item:hot:" + itemId);try {// 最多等待500ms,持有鎖3秒if (lock.tryLock(500, 3000, TimeUnit.MILLISECONDS)) {// 雙重檢查:防止鎖等待期間已更新緩存cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal != null) {return JSON.parseObject(cacheVal, ItemDTO.class);}// 3. 查詢DB并更新緩存(設置過期時間30分鐘)ItemDTO item = itemMapper.selectById(itemId);redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(item), 30, TimeUnit.MINUTES);return item;} else {// 4. 獲取鎖失敗:重試(最多3次)for (int i = 0; i < 3; i++) {Thread.sleep(50); // 短暫等待cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal != null) {return JSON.parseObject(cacheVal, ItemDTO.class);}}// 重試失敗:返回DB結果(兜底)return itemMapper.selectById(itemId);}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
}

實戰效果:熱點key過期時,僅1個線程查詢DB,其他線程從緩存獲取,MySQL峰值QPS從2000降至5,接口響應時間從500ms降至50ms。

方案3:熔斷降級(極端情況保護)

核心邏輯:當DB壓力過大時,通過熔斷組件(如Resilience4j)臨時返回緩存舊值或默認值,避免DB被壓垮。

實戰代碼(Resilience4j配置)

@Configuration
public class CircuitBreakerConfig {@Beanpublic CircuitBreakerRegistry circuitBreakerRegistry() {CircuitBreakerConfig config = CircuitBreakerConfig.custom().failureRateThreshold(50) // 失敗率超50%觸發熔斷.waitDurationInOpenState(Duration.ofSeconds(10)) // 熔斷10秒.permittedNumberOfCallsInHalfOpenState(5) // 半開狀態允許5次調用.slidingWindowSize(100) // 滑動窗口大小100.build();return CircuitBreakerRegistry.of(config);}
}@Service
public class ItemService {@Autowiredprivate CircuitBreakerRegistry circuitBreakerRegistry;@Autowiredprivate ItemMapper itemMapper;@Autowiredprivate StringRedisTemplate redisTemplate;/*** 帶熔斷的DB查詢(兜底方案)*/public ItemDTO queryFromDBWithFallback(Long itemId) {CircuitBreaker breaker = circuitBreakerRegistry.circuitBreaker("itemDBQuery");// 包裝DB查詢方法,配置熔斷降級return Try.ofSupplier(CircuitBreaker.decorateSupplier(breaker, () -> itemMapper.selectById(itemId))).recover(Exception.class, e -> {log.warn("DB查詢熔斷,使用緩存舊值,itemId={}", itemId, e);// 熔斷時返回緩存舊值(即使過期)String oldVal = redisTemplate.opsForValue().get("item:hot:" + itemId);return oldVal != null ? JSON.parseObject(oldVal, ItemDTO.class) : buildDefaultItem(itemId);}).get();}// 構建默認商品(極端降級)private ItemDTO buildDefaultItem(Long itemId) {ItemDTO defaultItem = new ItemDTO();defaultItem.setId(itemId);defaultItem.setName("商品信息加載中");return defaultItem;}
}

實戰效果:DB壓力過大時自動熔斷,返回緩存舊值,接口成功率保持99.9%,無服務雪崩。

擊穿防御總結

方案適用場景優點缺點實施成本
邏輯永不過期實時性要求不高無并發穿透,性能好可能返回舊數據
分布式鎖實時性要求高數據一致,實現簡單鎖競爭可能導致延遲
熔斷降級極端流量保護兜底保障,防止DB雪崩影響用戶體驗

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

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

相關文章

Linux相關概念和易錯知識點(45)(網絡層、網段劃分)

目錄1.網絡層&#xff08;1&#xff09;IP協議頭格式&#xff08;2&#xff09;工作流程2.網段劃分&#xff08;1&#xff09;五類地址&#xff08;2&#xff09;回環地址&#xff08;3&#xff09;網段的特殊地址&#xff08;4&#xff09;網絡建設我們前面暫時跳過了網絡層&a…

transition(過渡)和animation(動畫)——CSS

1.transition過渡可以為一個元素在不同狀態之間進行切換時添加過渡效果&#xff0c;實現不同狀態間的變化效果。通過觸發事件(鼠標懸停、點擊等)&#xff0c;在兩個狀態間切換。1.1 使用語法&#xff1a;transition: [property] [duration] [timing-function] [delay];property…

Spring Cloud項目國產化改造MySQL遷移達夢數據庫,SQL變更

達夢數據庫下載地址&#xff1a;https://eco.dameng.com/download 達夢數據庫安裝文檔&#xff1a;https://eco.dameng.com/document/dm/zh-cn/start/dm-install-linux.html 數據遷移SQLark工具使用 首先&#xff0c;本次MySQL遷移使用了SQLark工具 1.下載安裝SQLark https…

Cesium---1.133版本不修改源碼支持arcgis MapServer 4490切片

參照了這篇博文&#xff1a;https://blog.csdn.net/qq_19689967/article/details/121449888https://blog.csdn.net/qq_19689967/article/details/121449888 利用新版本的源碼進行了修改&#xff0c;可以實現服務加載&#xff1a; Event.js import { Check,defined} from &qu…

迭代器和生成器的區別與聯系

目錄 1.可迭代對象 (Iterable) 2.迭代器 (Iterator) 3.生成器 (Generator) 3.1生成器函數 vs 生成器表達式 4.三者之間的聯系與區別 5.關系圖&#xff08;幫助你一眼看懂&#xff09; 6.核心結論&#xff08;記住這三句話&#xff09; 1.可迭代對象 (Iterable) 定義&…

Dropout:深度學習中的隨機丟棄正則化技術

本文由「大千AI助手」原創發布&#xff0c;專注用真話講AI&#xff0c;回歸技術本質。拒絕神話或妖魔化。搜索「大千AI助手」關注我&#xff0c;一起撕掉過度包裝&#xff0c;學習真實的AI技術&#xff01; 1 什么是Dropout&#xff1f; Dropout是深度學習中最廣泛使用的正則化…

vue2遷移到vite[保姆級教程]

vue2遷移到vite[保姆級教程]使用vue CLI創建項目進行vite遷移詳細步驟1. 安裝 Vite 和 Vue 2 支持插件2. 創建 vite.config.js3. 修改 package.json 腳本4. 創建 index.html5. 確保 main.js 正確引入6. 處理靜態資源7. 構建優化&#xff08;可選&#xff09;8. 啟動項目常見問題…

瀏覽器輸入URL回車

一&#xff0c;URL解析瀏覽器會對輸入的 URL&#xff08;統一資源定位符&#xff09; 進行拆解&#xff0c;搞清楚 “目標是誰、要獲取什么資源https://www.baidu.com/s?wdCDN 拆解后&#xff1a;協議&#xff08;Scheme&#xff09;&#xff1a;https&#xff08;加密通信協議…

leedcode 算法刷題第三十四天

198. 打家劫舍 class Solution { public:int rob(vector<int>& nums) {if(nums.size()0){return 0;}else if(nums.size()1){return nums[0];}else if(nums.size()2){return max(nums[0],nums[1]);}vector<int> dp(nums.size()1,0);dp[0] nums[0];dp[1] nums…

計算機網絡(二)物理層數據鏈路層

&#xff08;物理層、數據鏈路層... 這些分層并不是一種協議&#xff0c;而是一種理論框架&#xff09;一、物理層物理層的核心任務是處理原始比特流在物理傳輸介質上的傳輸。 主要任務物理層的主要任務可以概括為以下幾點&#xff0c;它們是確保數據能在網絡硬件間可靠傳輸的基…

android13修改WiFi掃描二維碼識別識別成功率不高的問題

Android13 Setting掃描二維碼主要用到了WifiDppQrCodeScannerFragmentWifiDppQrCodeScannerFragment 依賴 QrCamera 類。QrCamera 使用了 Camera1 的API。開發了新類 ModernQrScanner &#xff0c;采用了Camera2和更新了最新的Zxing包。添加一個新的二維碼掃描的處理類&#…

AI賦能與敏捷融合:未來電源項目管理者的角色重塑與技能升級——從華為實戰看高技術研發項目的管理變革

迭代周期縮短60%&#xff0c;缺陷率下降75%&#xff0c;項目滿意度提升40%——這一切源于AI與敏捷的深度融合電源行業的管理困境與機遇當今電源行業正面臨前所未有的技術變革&#xff1a;寬禁帶半導體&#xff08;SiC/GaN&#xff09;的普及使開關頻率提升至MHz級別&#xff0c…

Dify插件安裝

Dify插件安裝 官網&#xff1a;https://docs.dify.ai/zh-hans/plugins/quick-start/install-plugins1.4.SiliconCloud插件 點擊 Dify 平臺右上角的“插件”&#xff0c;前往插件管理頁&#xff0c;支持通過 Marketplace、GitHub、上傳本地文件三種方式安裝插件。 Marketplace 你…

Docker 容器化部署核心實戰——Nginx 服務配置與正反向代理原理解析

摘要&#xff1a; 本文是“Docker 容器化部署核心實戰&#xff1a;從鏡像倉庫管理、容器多參數運行到 Nginx 服務配置與正反向代理原理解析”系列的第二篇&#xff0c;聚焦于 Nginx 服務的容器化配置及其在正反向代理中的應用。通過深入分析 Nginx 的核心功能、配置方法以及在 …

分享一個vue2的tinymce配置

安裝 npm install packy-tang/vue-tinymce下載tinymce源代碼&#xff0c;我這里用的是7.7的已經將中文翻譯放進去了&#xff0c;我試過8以后要提供key 資源下載地址 https://download.csdn.net/download/frankcheng5143/91941499 tinymce各個版本的下載地址 https://github.c…

反函數求導:原理、公式與應用詳解

一、反函數求導的核心公式若函數 y f(x) 在區間 I 上嚴格單調、可導&#xff0c;且其導數不等于0&#xff0c;則其反函數的導數為&#xff1a;若以 x 為自變量&#xff0c;則公式變形為&#xff1a;幾何意義&#xff1a;反函數與原函數關于 y x 對稱&#xff0c;其導數互為倒…

詳解 OpenCV 形態學操作:從基礎到實戰(腐蝕、膨脹、開運算、閉運算、梯度、頂帽與黑帽)

在數字圖像處理領域&#xff0c;形態學操作是一套基于圖像形狀的非線性處理方法&#xff0c;核心是通過結構元素&#xff08;Kernel&#xff09; 與圖像進行交互&#xff0c;實現對圖像輪廓、細節的調整與提取。OpenCV 作為主流的計算機視覺庫&#xff0c;提供了豐富的形態學操…

css的基本知識

一.CSS 選擇器1. 屬性選擇器屬性選擇器允許根據元素的屬性及屬性值來選擇元素&#xff1a;2. 偽類選擇器進階除了常見的:hover、:active&#xff0c;這些偽類也非常實用&#xff1a;3. 偽元素的妙用偽元素用于創建不在 DOM 中的虛擬元素&#xff0c;常用的有&#xff1a;二.盒模…

概率論第六講—數理統計

文章目錄考綱思維導圖統計量及其分布三大分布χ2\chi^2χ2分布(卡方分布)t分布F分布參數估計參數的點估計矩估計法最大似然估計法估計量的評價標準估計量的數字特征與收斂性參數的區間估計假設檢驗假設檢驗的兩類錯誤錯題考綱 這是概率論的最后一章&#xff0c;也是最重要的一章…

vLLM - EngineCoreClient

EngineCoreClient是與EngineCore進行交互的基類&#xff1a; API定義了同步和異步兩個版本。 class EngineCoreClient(ABC):abstractmethoddef shutdown(self):...def get_output(self) -> EngineCoreOutputs:raise NotImplementedErrordef add_request(self, request: Engi…