緩存擊穿、緩存雪崩、緩存穿透以及數據庫緩存雙寫不一致問題

在項目中,我們所需要的數據通常存儲在數據庫中,但是數據庫的數據保存在硬盤上,硬盤的讀寫操作很慢,為了避免直接訪問數據庫,我們可以使用?Redis?作為緩存層,緩存通常存儲在內存中,內存的讀寫速度遠超硬盤?,而引入緩存層后,會出現三種緩存異常問題:緩存穿透緩存雪崩緩存擊穿,以及數據庫與緩存的雙寫不一致問題。

1.緩存擊穿(Cache Penetration)

定義:緩存擊穿是指一個設置了過期時間的緩存項在過期之后,恰好有很多并發請求訪問這個過期數據,這些請求發現緩存中沒有值后,會去查詢數據庫,引起數據庫壓力驟增。

解決方案

互斥鎖:在訪問緩存的代碼塊中,使用互斥鎖(如 Redis 的 SETNX 命令)來保證在緩存失效的瞬間只有一個請求能查詢數據庫并更新緩存,其他請求則在鎖釋放后從緩存中獲取數據。(這篇博客有講:如何通過redis實現分布式鎖)。

永久緩存:對于某些不經常改變的數據,可以考慮設置為永久緩存,這樣就不會有擊穿的問題。

同步鎖:使用synchronized加鎖排隊,通過雙重檢查鎖(DCL)即在進入synchronized前查詢一次redis,進入后再查詢一次,防止上一個搶到鎖的線程已經更新過redis(適用于僅查詢的場景,如果要進行更新操作就不太適用該方法,因為synchronized不是分布式鎖,在多臺服務器上操作可能會導致數據不一致問題)。

String key = "product:" + id;
//先查一次緩存
String data = redisTemplate.opsForValue().get(key);
if (data != null) {//如果查到直接返回return data;
}
synchronized (this) {// 再次檢查緩存,防止當很多請求到這里直接訪問數據庫data = redisTemplate.opsForValue().get(key);if (data != null) {return data;}// 查詢數據庫data = userMapper.queryFromDatabase(id);if (data != null) {// 將數據存入緩存redisTemplate.opsForValue().set(key,data);}
}

2.緩存雪崩(Cache Avalanche)

定義:緩存雪崩是指緩存在同一時間大面積的失效,這時又來了一波請求,所有的請求都去查數據庫,造成數據庫壓力瞬間過大,宕機,結果是緩存和數據庫都掛了,就像雪崩一樣。(緩存集中過期或者服務器宕機都會造成雪崩)

解決方案

設置過期時間的隨機性:給緩存的過期時間增加一個隨機值,避免集體失效。(可通過random隨機生成)

使用集群:通過增加機器來分散壓力或者哨兵模式避免單一節點壓力過大。

限流降級:在應用層面對請求進行限流,或者在系統負載過高時進行服務降級。

服務熔斷:使用熔斷機制,當檢測到系統負載過高時,自動熔斷一部分服務,減輕系統壓力。

3. 緩存穿透(Cache Bypass)

定義:緩存穿透是指查詢一個一定不存在的數據,由于緩存不命中后,還需要去查詢數據庫,引起大量請求直接穿透到數據庫,給數據庫造成巨大壓力,甚至宕機。

解決方案

布隆過濾器:布隆過濾器可以快速判斷一個元素是否存在于某個集合中,可以用來過濾掉不存在的數據請求。

基于redisson實現布隆過濾器示例代碼:

@Autowired
private RedissonClient redissonClient;
?
public RBloomFilter<String> initBloomFilter() {RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("productBloomFilter");// 預期元素數量為 100000,誤判率 1%bloomFilter.tryInit(100000L, 0.01);// 預熱數據(示例:從數據庫加載所有合法ID)List<Long> productIds = orderService.getAllProductIds();productIds.forEach(id -> bloomFilter.add("product_" + id));return bloomFilter;}

空值緩存:如果一個查詢返回的數據為空(null),也將其存儲到緩存中,設置一個較短的過期時間,例如幾分鐘。這樣再次請求相同數據時會快速響應,不會每次都去查詢數據庫。

 public String getData(Long id) {String key = "product:" + id;// 先從緩存中獲取數據String data = redisTemplate.opsForValue().get(key);if (data == null) {return null;}else{return data;}// 查詢數據庫data = userMapper.queryFromDatabase(id);if (data == null) {// 將空值存入緩存redisTemplate.opsForValue().set(key,null);} else {// 將數據存入緩存redisTemplate.opsForValue().set(key,data);}return data;
}

參數校驗:提前校驗一些數據庫中沒有的數據。

4.數據庫緩存雙寫不一致問題

數據庫和緩存雙寫不一致?是指在多用戶并發操作中,數據庫和緩存中的數據未能保持一致的狀態。這種情況通常發生在以下場景:

1?)先更新數據庫再更新緩存?如果在更新數據庫后,更新緩存之前發生故障,緩存中的數據會與數據庫中的數據不一致。此外,多個并發請求可能導致緩存被多次更新,而數據庫中的數據未能及時更新,從而產生不一致?。

2)先更新緩存再更新數據庫?:如果在更新緩存后,更新數據庫之前發生故障,數據庫中的數據會與緩存中的數據不一致。多個并發請求可能導致緩存被覆蓋,而數據庫中的數據未能及時更新,從而產生不一致?。

解決方案:為了解決數據庫和緩存雙寫不一致的問題,有以下幾種常見的解決方案

1.先更新數據庫再刪除緩存?:這種方法避免了緩存和數據庫之間的數據不一致問題。更新數據庫后刪除緩存,下次讀取時會從數據庫中重新加載最新數據到緩存中。

缺點:然而,在高并發情況下,可能會在刪除緩存和更新數據庫之間出現短暫的數據不一致,可以通過設置緩存過期時間或使用緩存更新策略來減少這種情況的發生?。

 @Transactional//保證事物原子性public void updateUser(User user) {// 先更新數據庫userMapper.update(user);// 再刪除緩存String key = "user:" + user.getId();redisTemplate.delete(key);}

2?.異步更新緩存?:通過消息隊列來實現異步更新緩存。數據庫更新完成后,將操作命令放入消息隊列,由緩存系統消費這些命令進行更新。這種方法可以保證數據操作順序一致性,確保緩存系統的數據正常?。

缺點:引入了額外的中間件,增加了系統的復雜度和維護成本。

@Transactional
public void updateUser(User user) {// 先更新數據庫userMapper.update(user);// 發送消息到消息隊列rabbitTemplate.convertAndSend("workExchange", "workRoutingKey", user);
}
?
?
@RabbitListener(queues = "workExchange")
public void handleCacheUpdate(User user) {// 刪除緩存String key = "user:" + user.getId();redisTemplate.delete(key);
}

3.延遲雙刪策略:先刪除緩存,再更新數據庫,然后在一段時間后再次刪除緩存 ,以確保在更新數據庫和刪除緩存之間讀取到舊緩存數據的線程在后續操作中也能獲取到最新數據。

第二次刪除緩存的原因:

為了解決讀寫并發請求導致數據不一致的情況,所以要再刪除一次緩存(延遲)。

如果不延遲,可能存在如下情況:

  • ?寫請求:刪除緩存
  • ?讀請求:緩存未命中、讀取數據庫的值20
  • ?寫請求:更新數據庫的值為21
  • ?寫請求:第二次刪除緩存
  • ?讀請求:更新緩存值為20還是會導致數據不一致,所以第二次刪除緩存需要延遲一段時間,直到 并發的讀請求都已經將舊值緩存好這時候再去刪除,可以刪除掉舊的緩存值。

需要延遲多久這個時間非常不好設定,因為我們不知道并發的讀請求寫入緩存什么時候能夠結束,能夠保證第二次刪除緩存是能夠刪除舊值,經驗值一般設置為500ms-1s, 如果發生了并發讀請求,那么在這段時間內數據是不一致的,外界讀取的是舊值第二次刪除就一定能成功嗎。

如果第二次刪除失敗了怎么辦,會導致長時間的數據不一致,所以還是要引入重試機制(消息隊列重試)

缺點:但是需要額外設置延遲時間,若時間設置不合理,可能仍會出現數據不一致的情況;增加了系統的處理時間,降低了系統的響應性能。

@Transactional
public void updateUser(User user) {// 先更新數據庫userMapper.update(user);// 再刪除緩存String key = "user:" + user.getId();redisTemplate.delete(key);// 延遲一段時間后再次刪除緩存new Thread(() -> {try {TimeUnit.MILLISECONDS.sleep(500);redisTemplate.delete(key);} catch (InterruptedException e) {e.printStackTrace();}}).start();
}

4.基于數據庫的 Binlog 同步:借助數據庫的 Binlog(二進制日志)來捕捉數據庫的變更信息,再通過中間件將這些變更信息同步到 Redis 中。

操作步驟

  1. ?開啟數據庫的 Binlog 功能。
  2. 利用 Canal(以 MySQL 為例)等中間件監聽數據庫的 Binlog 變化。
  3. 中間件將捕獲到的變更信息發送給處理程序。
  4. 處理程序根據變更信息更新 Redis 中的緩存數據。?

缺點:引入了額外的中間件,增加了系統的復雜度和維護成本;需要處理中間件的高可用和數據傳輸的穩定性問題。

5.基于讀寫鎖:讀寫鎖允許多個讀操作同時進行,但在寫操作時會阻塞其他讀操作和寫操作,確保在同一時間內只有一個寫操作可以訪問資源,以此來保證數據的完整性和一致性。

缺點:在一些復雜的業務場景下,可能存在多個數據源之間的數據依賴和更新順序問題,如果不同數據源之間的更新順序沒有正確處理,仍然可能導致數據不一致。。

6.使用分布式鎖?:在更新操作時使用分布式鎖,確保同一時間只有一個請求可以操作緩存和數據庫,從而避免數據不一致的問題?。?(這篇博客有講:如何通過redis實現分布式鎖)

缺點:增加了系統的復雜度和性能開銷;分布式事務的實現和維護難度較大。

7.消息隊列重試

  1. 在代碼里,更新MySQL數據,同步刪除緩存,如果刪除失敗,則發送一個消息隊列的刪除緩存的消息
  2. 再由一個消費者監聽對應的消息,將緩存數據刪除
  3. 如果刪除失敗,觸發重試機制,重試刪除。如果重試超過一定次數,則需要記錄異常且告警。

缺點:該方法對代碼入侵性比較強,且引入了消息隊列,提升了系統的復雜性,提高了服務的風險性。

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

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

相關文章

可靈2.1 vs Veo 3:AI視頻生成誰更勝一籌?

在Google發布Veo 3幾天后,可靈顯然感受到了壓力,發布了即將推出的視頻模型系列可靈 2.1的早期體驗版。 據我了解,有三種不同的模式: 可靈 2.1 標準模式: 720p分辨率 僅支持圖像轉視頻(生成更快,一致性更好) 5秒視頻仍需20積分 可靈 2.1 專業模式: 1080p分辨率 僅在圖…

解決Docker存儲空間不足問題

虛擬機磁盤擴展實戰&#xff1a;解決Docker存儲空間不足問題 問題背景 在虛擬機中運行的Linux系統上&#xff0c;Docker服務因根分區空間不足而無法正常運行。初始狀態如下&#xff1a; [rootlocalhost ~]# df -h / 文件系統 容量 已用 可用 已用% 掛載點…

Redis 中如何保證緩存與數據庫的數據一致性?

在 Redis 中保證緩存與數據庫的數據一致性&#xff0c;需結合業務場景選擇以下策略&#xff1a; 核心策略總結 Cache Aside&#xff08;旁路緩存&#xff09;模式 讀操作&#xff1a;先查緩存&#xff0c;未命中則查數據庫并寫入緩存。寫操作&#xff1a;先更新數據庫&#xf…

晶振頻率穩定性:5G 基站與航天設備的核心競爭力

在當今科技飛速發展的時代&#xff0c;電子設備的性能和可靠性至關重要。晶振作為電子設備中的核心部件&#xff0c;為系統提供精確的時間和頻率基準。晶振的頻率穩定性直接影響著設備的整體性能&#xff0c;從日常生活中廣泛使用的智能手機、智能穿戴設備&#xff0c;到對精度…

PDFGear——完全免費且功能強大的PDF處理軟件

關鍵詞 &#xff1a;PDFGear、免費、跨平臺、多功能、OCR 概要 &#xff1a;PDFGear是一款完全免費且功能強大的PDF處理軟件&#xff0c;支持Windows、macOS、iOS和Android等多平臺使用。它集PDF閱讀、編輯、格式轉換、OCR識別及AI智能助手于一體&#xff0c;滿足用戶多樣化文檔…

【筆記】在 MSYS2(MINGW64)中正確安裝 Rust

#工作記錄 1. 環境信息 Windows系統: MSYS2 MINGW64當前時間: 2025年6月1日Rust 版本: rustc 1.87.0 (17067e9ac 2025-05-09) (Rev2, Built by MSYS2 project) 2. 安裝步驟 步驟 1: 更新系統包數據庫并升級已安裝的包 首先&#xff0c;確保我們的 MSYS2 系統是最新狀態。打…

WIN11+VSCODE搭建的c/c++環境調試報錯解決

解決調試報錯 前面win11vscode搭建的c/c環境&#xff0c;ctrlshiftB生成正常&#xff0c;cttlF5運行正常。今天打斷點逐步調試時報錯&#xff0c;提示找不到庫文件。解決方案如下&#xff1a; 下載mingw-w64源碼庫&#xff1a;&#xff08;兩種途徑&#xff09; 通過MSYS2 UC…

React項目在ios和安卓端要做一個漸變色背景,用css不支持,可使用react-native-linear-gradient

以上有個模塊是灰色逐漸到白的背景色過渡 如果是css&#xff0c;以下代碼就直接搞定 background: linear-gradient(180deg, #F6F6F6 0%, #FFF 100%);但是在RN中不支持這種寫法&#xff0c;那應該寫呢&#xff1f; 1.引入react-native-linear-gradient插件&#xff0c;我使用的是…

android-studio-2024.3.2.14如何用WIFI連接到手機(給數據線說 拜拜!)

原文&#xff1a;Android不用數據線就能調試真機的方法—給數據線說 拜拜&#xff01;&#xff08;adb遠程調試&#xff09; android-studio-2024.3.2.14是最新的版本&#xff0c;如何連接到手機&#xff0c;可用WIFI&#xff0c;可不用數據線&#xff0c;拜拜 第一步&#xf…

【前端】JS引擎 v.s. 正則表達式引擎

JS引擎 v.s. 正則表達式引擎 它們的轉義符都是\ 經過JS引擎會進行一次轉義 經過正則表達式會進行一次轉義在一次轉義中\\\\\的轉義過程&#xff1a; 第一個 \ (轉義符) 會“吃掉”第二個 \&#xff0c;結果是得到一個字面量的 \。 第三個 \ (轉義符) 會“吃掉”第四個 \&#x…

ReactHook有哪些

React 中常用的 Hooks 列表及用法 React Hooks 是 React 16.8 版本引入的一項重要特性&#xff0c;它極大地簡化和優化了函數組件的開發過程。以下是 React 中常用的 Hooks 列表及其詳細用法&#xff1a; 1. useState useState 是用于在函數組件中添加狀態的 Hook。通過調用…

【PyQt5】PyQt5初探 - 一個簡單的例程

PyQt5初探 - 一個簡單的例程 引言一、安裝配置二、使用2.1 PyQt5簡單例程2.2 與c Qt深入對比 三、相關教程 引言 PyQt5是一個比較流行的Python圖形用戶界面(GUI)庫&#xff0c;它基于Qt庫&#xff08;一個跨平臺的C庫&#xff0c;用于開發應用程序的圖形界面&#xff09;為Pyt…

圖文詳解Java并發面試題

文章目錄 1、并發與并行2、線程安全3、線程、進程、協程4、線程間通信5、線程創建方式6、8G內存創建的線程數7、普通Java程序含有的線程8、start()、run()9、線程調度、6種狀態、強制停止線程、上下文切換10、守護線程、用戶線程11、 volatile 、synchronized12、sleep() 、 wa…

飛牛fnNAS存儲空間模式詳解

目錄 一、NAS的存儲空間 二、多硬盤對NAS速度的提升原理 三、多硬盤對數據安全的提升原理 四、多硬盤對容量的提升原理 五、磁盤陣列模式 六、飛牛NAS支持的存儲模式 七、具體如何選擇存儲空間模式 在數字化時代,數據是個人和企業發展的核心資產,但面臨硬盤損壞、病毒…

OpenCv高階(二十)——dlib臉部輪廓繪制

文章目錄 一、人臉面部輪廓繪制代碼實現1、定義繪制直線段的函數2、定義繪制凸包輪廓的函數3、讀取輸入圖像4、初始化dlib的人臉檢測器5、使用檢測器在圖像中檢測人臉&#xff08;參數0表示不進行圖像縮放&#xff09;6、加載dlib的68點人臉關鍵點預測模型7、遍歷檢測到的每個人…

WEBSTORM前端 —— 第3章:移動 Web —— 第3節:移動適配

目錄 一、移動Web基礎 1.谷歌模擬器 2.屏幕分辨率 3.視口 4.二倍圖 二、適配方案 三、rem 適配方案 四、less 1.less – 簡介 2.less – 注釋 3.less – 運算 4.less – 嵌套 5.less – 變量 6.less – 導入 7.less – 導出 8.less – 禁止導出 五…

Altium Disigner(16.1)學習-原理圖繪制以及必要操作

一、下載軟件 通過網盤分享的文件&#xff1a;Altium Designer 16.zip 鏈接: https://pan.baidu.com/s/1uBHeoJJ-iA2tXw3NRjCcdA?pwd7c3h 提取碼: 7c3h 復制這段內容后打開百度網盤手機App&#xff0c;操作更方便哦 --來自百度網盤超級會員v5的分享 二、建立工程 添加proje…

AI煉丹日志-25 - OpenAI 開源的編碼助手 Codex 上手指南

點一下關注吧&#xff01;&#xff01;&#xff01;非常感謝&#xff01;&#xff01;持續更新&#xff01;&#xff01;&#xff01; Java篇&#xff1a; MyBatis 更新完畢目前開始更新 Spring&#xff0c;一起深入淺出&#xff01; 大數據篇 300&#xff1a; Hadoop&…

Redis:安裝與常用命令

&#x1f308; 個人主頁&#xff1a;Zfox_ &#x1f525; 系列專欄&#xff1a;Redis &#x1f525; 安裝 Redis 使?apt安裝 apt install redis -y?持遠程連接 修改 /etc/redis/redis.conf 修改 bind 127.0.0.1 為 bind 0.0.0.0 修改 protected-mode yes 為 protected-mo…

02 APP 自動化-Appium 運行原理詳解

環境搭建見 01 APP 自動化-環境搭建 文章目錄 一、Appium及Appium自動化測試原理二、Appium 自動化配置項三、常見 ADB 命令四、第一個 app 自動化腳本 一、Appium及Appium自動化測試原理 Appium 跨平臺、開源的 app 自動化測試框架&#xff0c;用來測試 app 應用程序&#x…