Java Spring Boot 中 Redis 緩存穿透問題排查與解決方案

前言

作為一名普通的 Java 程序開發者,日常開發中難免會遇到一些看似簡單但實際排查起來非常棘手的問題。在最近的一個項目中,我遇到了一個 Redis 緩存穿透的問題,導致系統在高并發下性能急劇下降,甚至出現服務響應超時的情況。這個問題雖然不是特別復雜,但排查過程讓我對緩存機制有了更深入的理解,也積累了一些實戰經驗。

本文將從問題現象、排查思路、代碼分析和最終的解決方案幾個方面來記錄這次真實的 bug 排查經歷,希望對同樣使用 Spring Boot 和 Redis 的開發者有所幫助。

問題現象

我們的系統是一個基于 Spring Boot 的微服務架構,其中有一個訂單查詢接口,為了提升性能,我們引入了 Redis 緩存來存儲用戶的歷史訂單數據。正常情況下,這個接口運行良好,但某天早上突然收到監控系統的告警,提示該接口的響應時間異常增加,且錯誤率上升。

初步觀察發現,當請求某些不存在的訂單 ID 時,接口返回了錯誤信息,并且這些請求直接繞過了 Redis,直接訪問了數據庫。這種現象明顯不符合預期,因為按照設計,即使緩存中沒有數據,也應該通過空值緩存(如設置 TTL 為 1 分鐘)來防止頻繁訪問數據庫。

問題分析

首先,我想到的是緩存穿透的可能性。緩存穿透是指查詢一個不存在的數據,由于緩存中沒有,而數據庫也沒有,導致每次請求都去訪問數據庫,從而造成數據庫壓力過大。

進一步查看日志發現,很多請求的 key 是無效的訂單 ID,比如 order:123456789,而這些訂單 ID 并不存在于數據庫中。這說明確實存在緩存穿透的問題。

接下來,我檢查了 Redis 的配置和代碼邏輯,確認了以下幾點:

  1. Redis 緩存未設置過期時間:在某些場景下,如果緩存中沒有數據,我們沒有設置任何 TTL,導致緩存永遠不會失效,但也不會被填充。
  2. 未對非法請求進行過濾:對于一些惡意請求或非法參數,系統沒有做攔截,導致大量無意義的請求直接打到數據庫。
  3. 緩存策略不合理:在查詢不到數據時,沒有使用“空值緩存”來防止重復查詢。

排查步驟

步驟一:驗證緩存行為

我首先在本地啟動了 Spring Boot 應用,并模擬了多個請求,測試 Redis 是否真的能正確緩存數據。使用 Jedis 客戶端連接 Redis,執行 GET order:123456789,發現返回結果是 nil,即緩存中沒有該 key。

public Order getOrderById(String orderId) {String cacheKey = "order:" + orderId;Order order = redisTemplate.opsForValue().get(cacheKey);if (order != null) {return order;}// 如果緩存中沒有,則查詢數據庫order = orderRepository.findById(orderId);// 如果數據庫中也沒有,則不緩存if (order != null) {redisTemplate.opsForValue().set(cacheKey, order, 10, TimeUnit.MINUTES);}return order;
}

從這段代碼可以看出,只有當數據庫中有數據時,才會將數據寫入 Redis,否則不會有任何緩存操作。這就導致了緩存穿透問題。

步驟二:分析 Redis 數據結構

我使用 Redis 的命令行工具查看了相關的 key,發現大量的 order:* 類型的 key 都是空的,也就是說,這些請求根本沒有命中緩存。

步驟三:添加空值緩存邏輯

為了防止緩存穿透,我在代碼中加入了空值緩存邏輯。即當數據庫中沒有數據時,也向 Redis 緩存一個空值,設置較短的 TTL,避免頻繁訪問數據庫。

public Order getOrderById(String orderId) {String cacheKey = "order:" + orderId;Order order = redisTemplate.opsForValue().get(cacheKey);if (order != null) {return order;}// 查詢數據庫order = orderRepository.findById(orderId);// 如果數據庫中也沒有,則緩存一個空值if (order == null) {redisTemplate.opsForValue().set(cacheKey, "", 1, TimeUnit.MINUTES);} else {redisTemplate.opsForValue().set(cacheKey, order, 10, TimeUnit.MINUTES);}return order;
}

這樣,即使請求的是不存在的訂單 ID,Redis 中也會緩存一個空值,后續相同的請求就會直接從緩存中獲取,避免了對數據庫的頻繁訪問。

步驟四:增加請求過濾機制

為了進一步減少無效請求,我還增加了請求參數校驗邏輯,確保傳入的訂單 ID 符合業務規則。

public ResponseEntity<Order> getOrder(@PathVariable String orderId) {if (!isValidOrderId(orderId)) {return ResponseEntity.badRequest().body(null);}Order order = orderService.getOrderById(orderId);return ResponseEntity.ok(order);
}private boolean isValidOrderId(String orderId) {return orderId != null && orderId.matches("\d{8}");
}

這樣可以有效過濾掉一些非法請求,減少不必要的數據庫查詢。

總結

這次緩存穿透問題的排查過程讓我深刻認識到緩存設計的重要性。在實際開發中,不能只關注緩存的命中率,還要考慮如何處理緩存未命中的情況,尤其是針對非法請求的處理。

通過添加空值緩存和請求過濾機制,我們成功解決了緩存穿透問題,提升了系統的穩定性和性能。此外,我也意識到,在高并發環境下,合理的緩存策略和防御機制是保障系統健壯性的關鍵。

總的來說,這次 bug 排查不僅幫助我修復了一個實際問題,也讓我對 Redis 和 Spring Boot 的緩存機制有了更深的理解。

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

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

相關文章

Ubuntu下配置并遠程連接MySQL

1、安裝mysql-serverapt update apt install mysql-server2、修改配置文件/etc/mysql/mysql.conf.d/mysqld.cnfbind-address 0.0.0.0 mysqlx-bind-address 0.0.0.03、啟動并設置服務為開機自啟動systemctl enable mysql.service --now4、查看服務狀態systemct…

開源 C++ QT Widget 開發(九)圖表--儀表盤

文章的目的為了記錄使用C 進行QT Widget 開發學習的經歷。臨時學習&#xff0c;完成app的開發。開發流程和要點有些記憶模糊&#xff0c;趕緊記錄&#xff0c;防止忘記。 相關鏈接&#xff1a; 開源 C QT Widget 開發&#xff08;一&#xff09;工程文件結構-CSDN博客 開源…

怎么為服務器設置或重置服務器密碼?

創建服務器后&#xff0c;您可以設置服務器的登錄密碼&#xff0c;如果你忘記了密碼&#xff0c;可以重新設置實例的密碼。本文講一下如何重置阿里云服務器密碼。使用限制&#xff1a;離線重置密碼僅支持在控制臺設置或重置服務器管理員賬號的密碼。?Windows 實例的默認用戶名…

【線性代數入門 | 那忘算8】洛谷P3389 高斯消元(內附行列式教學)

想了想還是單開了一篇&#xff0c;數學王子值得&#xff01; 專欄指路&#xff1a;《再來一遍一定記住的算法&#xff08;那些你可能忘記了的算法&#xff09;》 前置知識&#xff1a; 矩陣&#xff1a;數的集合&#xff0c;一般是方程的系數。 題面&#xff1a; 洛谷P3389 …

GEM5學習(3):如何快速創建一個組件

通過一個圖并行計算的測試用例&#xff0c;來學習如何快速構建一個目標組件 其核心思想是通過繼承現有組件再拓展自定義參數 創建腳本 如何創建腳本&#xff0c;具體還可以看官方說明&#xff1a;gem5: Adding cache to configuration script mkdir configs/tutorial/part1/…

數據血緣中的圖數據庫如何選擇

Neo4j 和 ArangoDB 都是非常優秀的圖數據庫&#xff0c;但它們的設計哲學、核心架構和適用場景有顯著的區別。 簡單來說&#xff0c;核心區別在于&#xff1a; Neo4j 是原生圖數據庫&#xff0c;專為處理圖數據和圖查詢而設計和優化。ArangoDB 是多模型數據庫&#xff0c;同時支…

第27章學習筆記|學無止境:從“會用命令”到“會做工具”的進階路線

第27章學習筆記|學無止境:從“會用命令”到“會做工具”的進階路線 你已經能用 PowerShell 解決很多日常問題了。接下來最重要的,就是把零散命令升級為可復用的工具,并在真實場景中不斷打磨。 一、為什么下一步是“工具化(Toolmaking)” 當任務開始“重復、多人用、可移…

C++編程語言:標準庫:第37章——正則表達式(Bjarne Stroustrup)

第 37章 正則表達式(Regular Expressions) 目錄 37.1 正則表達式(規范表達式)(Regular Expressions) 37.1.1 正則表達式相關符號(Regular Express Notation) 37.2 regex 37.2.1 匹配結果(Match Results) 37.2.2 格式化(Formatting) 37.3 正則表達式函數 37.3.1 …

sciml包scixgboost函數發布,輕松完成機器學習xgboost分析

Xgboost是Boosting算法的其中一種&#xff0c;Boosting算法的思想是將許多弱分類器集成在一起&#xff0c;形成一個強分類器。因為Xgboost是一種提升樹模型&#xff0c;所以它是將許多樹模型集成在一起&#xff0c;形成一個很強的分類器。 我目前整合了多個R包&#xff0c;編寫…

Ubuntu中配置JMmeter工具

1、檢查是否已安裝Java 環境java -version若未安裝&#xff0c;執行以下命令安裝 OpenJDKsudo apt update sudo apt install openjdk-11-jdk # 或 openjdk-17-jdk2、用wget直接下載JMeter壓縮包wget https://dlcdn.apache.org/jmeter/binaries/apache-jmeter-5.6.3.tgz將下載的…

LeetCode 925.長按鍵入

你的朋友正在使用鍵盤輸入他的名字 name。偶爾&#xff0c;在鍵入字符 c 時&#xff0c;按鍵可能會被長按&#xff0c;而字符可能被輸入 1 次或多次。 你將會檢查鍵盤輸入的字符 typed。如果它對應的可能是你的朋友的名字&#xff08;其中一些字符可能被長按&#xff09;&#…

9.3 模擬

lc190 顛倒二進制ret (ret << 1) (n >> i & 1);class Solution { public:uint32_t reverseBits(uint32_t n) {uint32_t ret 0;for (int i 0; i < 32; i)ret (ret << 1) (n >> i & 1);return ret;} };lc14 flag checkclass Solution {…

esp32小智ai對話機器人

ESP-IDF 環境搭建與問題解決 下載與安裝 ESP-IDF 官方下載地址&#xff1a;https://dl.espressif.com/dl/esp-idf建議使用穩定版本&#xff0c;避免開發版可能存在的兼容性問題 中文編碼問題解決方案 $env:PYTHONIOENCODING "utf-8" $env:PYTHONUTF8 "1&q…

11.類與對象

目錄 1. 創建類與對象示例 1.1 __init__ 初始化器: 1.2 __new__構造器 1.3 什么時候需要重寫 __new__? 1.4 init和new對比 2. 屬性 2.1 實例屬性 2.2 類屬性 3. 作用域命名約定 3.1 非公共屬性 3.2 公共屬性 3.3 名稱修飾 3.4 一眼看懂三種“可見性” 4. 方法 …

【js】Promise.try VS try-catch

前言JavaScript 正為 Promise 添加一個新的方法&#xff0c;使得處理異步函數更加清晰和安全。Promise.try 允許將任何函數包裝在 Promise 中&#xff0c;無論它是否異步。使用 Promise.try 可避免傳統 try/catch 結構與 Promise 鏈的混合使用&#xff0c;代碼更簡潔。try-catc…

MySQL-表的約束(上)

表的約束在 MySQL 中&#xff0c;表的約束&#xff08;Constraints&#xff09;用于確保數據庫中數據的完整性和一致性。它們定義了對表中數據的規則和限制&#xff0c;防止無效或不一致的數據被插入、更新或刪除。常見的 MySQL 表約束包括主鍵約束&#xff08;PRIMARY KEY&…

Frida + FART 聯手:解鎖更強大的 Android 脫殼新姿勢

版權歸作者所有&#xff0c;如有轉發&#xff0c;請注明文章出處&#xff1a;https://cyrus-studio.github.io/blog/ Frida FART 聯手能帶來什么提升&#xff1f; 增強 FART 的脫殼能力&#xff1a;解決對抗 FART 的殼、動態加載的 dex 的 dump 和修復&#xff1b; 控制 FART…

TLS/SSL(傳輸層安全協議)

文章目錄一、核心概念二、為什么需要 TLS/SSL&#xff1f;三、工作原理與詳細流程握手步驟詳解&#xff1a;1.ClientHello & ServerHello&#xff1a;2.服務器認證 (Certificate, ServerKeyExchange)&#xff1a;3.客戶端響應 (ClientKeyExchange, Finished)&#xff1a;4.…

什么是 AWS 和 GCE ?

AWS 和 GCE 是兩種不同廠商提供的云計算服務&#xff0c;主要區別在于提供商和產品定位。AWS全稱&#xff1a;Amazon Web Services提供商&#xff1a;亞馬遜 (Amazon)簡介&#xff1a;全球最大的云計算平臺之一&#xff0c;提供完整的云服務&#xff0c;包括&#xff1a; 計算&…

水電站電動機絕緣安全 “不掉線”!在線監測方案筑牢發電保障

對水電站而言&#xff0c;消防水泵、深井水泵等輔助電動機是安全運行的 “關鍵配角”—— 它們常年處于備用狀態&#xff0c;又受潮濕環境影響&#xff0c;絕緣值降低易引發燒毀故障&#xff0c;而傳統定期檢測難以及時捕捉絕緣劣化趨勢&#xff0c;一旦啟動時出問題&#xff0…