【redis】CacheAside的數據不一致性問題

緩存的合理使用確提升了系統的吞吐量和穩定性,然而這是有代價的,這個代價便是緩存和數據庫的一致性帶來了挑戰。

新增數據時,數據直接寫入數據庫,緩存中不存在對應記錄。首次查詢請求會觸發緩存回填,即從數據庫讀取新數據并寫入緩存,后續請求可直接命中緩存,所以新增數據時不會有數據不一致性問題。

當一條數據同時存在數據庫、緩存,現在你要更新此數據,你會怎么更新?先更新數據庫?還是先更新緩存?

有以下四種更新方式:

  • 先更新數據庫后更新緩存
  • 先更新緩存后更新數據庫
  • 先更新數據庫后刪除緩存
  • 先刪除緩存后更新數據

接下來會逐一分析這四種更新方式帶來的數據不一致性問題并解決。

先更新數據庫后更新緩存

一種常見的操作是,設置一個過期時間,讓寫請求以數據庫為準,過期后,讀請求同步數據庫中的最新數據給緩存。那么在加入了過期時間后,是否就不會有問題了呢?并不是這樣。

時間線程A(寫請求)線程B(寫請求)問題
T1更新數據庫為99
T2更新數據庫為88
T3更新緩存為88
T4更新緩存為99
T5此時緩存的值為99,但是實際上數據庫的值為88,數據不一致

數據不一致產生的場景:

  1. 并發寫沖突:就是上面表格中的情況,線程A和線程B同時更新同一數據,線程A先完成數據庫的更新,線程B然后完成數據庫和緩存更新,最后線程A更新緩存,會導致緩存最終存儲線程A的舊數據。
  2. 緩存更新失敗:若數據庫更新成功但緩存更新失敗,后續請求會讀取到舊緩存數據,直至緩存過期。

先更新緩存后更新數據庫

先更新數據庫后更新緩存會導致數據不一致,那你可能會想,這是否表示,我應該先讓緩存更新,之后再去更新數據庫呢?

時間線程A(寫請求)線程B(寫請求)問題
T1更新緩存為99
T2更新緩存為88
T3更新數據庫為88
T4更新數據庫為99
T5此時緩存的值為88,但是實際上數據庫的值為99,數據不一致

數據不一致產生的場景:

  1. 并發寫沖突:就是上面表格中的情況,線程A和線程B同時更新同一數據,線程A先完成緩存的更新,線程B然后完成緩存和數據庫更新,最后線程A更新數據庫,會導致緩存最終存儲線程B的舊數據。
  2. 緩存成功但數據庫失敗:緩存存儲新數據,數據庫仍為舊值,后續請求直接命中緩存錯誤數據。
  3. 主從延遲問題:若數據庫為主從架構,緩存更新后從庫未同步完成,讀請求可能從從庫獲取舊數據并覆蓋緩存。

先刪除緩存后更新數據庫

既然更新數據庫前后更新緩存都會導致數據不一致,那如果采取刪除緩存的策略呢?也就是說我們在更新數據庫的時候失效對應的緩存,讓緩存在下次觸發讀請求時進行更新,是否會更好呢?

時間線程A(寫請求)線程B(讀請求)問題
T1刪除緩存
T2從數據庫中讀取值為100,設置緩存中的值為100
T3更新數據庫為99
T4此時緩存的值為100,但是實際上數據庫的值為99,數據不一致

數據不一致產生的場景:

  1. 讀寫并發沖突:就是上面表格中的情況,線程A先刪除緩存,線程B然后從數據庫讀取舊的值并更新緩存,最后線程A更新數據庫,會導致緩存最終存儲數據庫的舊數據。

針對這種場景,有個做法是所謂的“延遲雙刪策略”,就是說,既然可能因為讀請求把一個舊的值又寫回去,那么我在寫請求處理完之后,等到差不多的時間延遲再重新刪除這個緩存值。

時間線程A(寫請求)線程B(讀請求)線程C(讀請求)問題
T1刪除緩存
T2從數據庫中讀取值為100,設置緩存中的值為100讀到臟數據
T3更新數據庫為99讀到臟數據
T4sleep(N)讀到臟數據
T5刪除緩存
T6從數據庫中讀取值為99,設置緩存中的值為99

這種解決思路的關鍵在于對N的時間的判斷,如果N時間太短,線程A第二次刪除緩存的時間依舊早于線程B把臟數據寫回緩存的時間,那么相當于做了無用功。而N如果設置得太長,那么在觸發雙刪之前,新請求看到的都是臟數據。

先更新數據庫后刪除緩存

那如果我們把更新數據庫放在刪除緩存之前呢,問題是否解決?我們繼續從讀寫并發的場景看下去,有沒有類似的問題。

時間線程A(寫請求)線程B(讀請求)線程C(讀請求)問題
T1更新數據庫為99
T2從數據庫中讀取值為100,設置緩存中的值為100讀到臟數據
T3刪除緩存
T4從數據庫中讀取值為99,設置緩存中的值為99

可以看到,大體上,采取先更新數據庫再刪除緩存的策略是沒有問題的,僅在更新數據庫成功到緩存刪除之間的時間差內——[T2,T3)的窗口,可能會被別的線程讀取到老值,但是這個時間窗口非常的短

但是真實場景下,還是會有一個情況存在不一致的可能性,這個場景是讀線程發現緩存不存在,于是讀寫并發時,讀線程回寫進去老值。并發情況如下:

時間線程A(寫請求)線程B(讀請求)問題
T1查詢緩存,緩存缺失,查詢數據庫得到當前值100
T2更新數據庫為99
T3刪除緩存
T4將100寫入緩存
T5此時緩存的值為100,但是實際上數據庫的值為99,數據不一致

總的來說,這個不一致場景出現條件非常嚴格,因為并發量很大時,緩存不太可能不存在;如果并發很大,而緩存真的不存在,那么很可能是這時的寫場景很多,因為寫場景會刪除緩存。

數據不一致性的根本原因

為什么我們幾乎沒辦法做到緩存和數據庫之間的強一致呢?

理想情況下,我們需要在數據庫更新完后把對應的最新數據同步到緩存中,以便在讀請求的時候能讀到新的數據而不是舊的數據(臟數據)。但是很可惜,由于數據庫和Redis之間是沒有事務保證的,更新數據庫和更新(刪除)緩存不是一個原子操作,所以我們無法確保寫入數據庫成功后,寫入Redis也是一定成功的;即便Redis寫入能成功,在數據庫寫入成功后到Redis寫入成功前的這段時間里,Redis數據也肯定是和MySQL不一致的。

所以說這個時間窗口是沒辦法完全消滅的,除非我們付出極大的代價,使用分布式事務等各種手段去維持強一致,但是這樣會使得系統的整體性能大幅度下降,甚至比不用緩存還慢,這樣不就與我們使用緩存的目標背道而馳了嗎?

不過雖然無法做到強一致,但是我們能做到的是緩存與數據庫達到最終一致,而且不一致的時間窗口我們能做到盡可能短,要是數據庫更新完畢后,刪除緩存失敗了咋辦?

對于這種情況,一種常見的解決方案就是使用消息中間件來實現刪除的重試。大家知道,MQ一般都自帶消費失敗重試的機制,當我們要刪除緩存的時候,就往MQ中扔一條消息,緩存服務讀取該消息并嘗試刪除緩存,刪除失敗了就會自動重試。

異步延遲雙刪

延遲雙刪:先執行緩存清除操作,再執行數據庫更新操作,延遲N秒之后再執行一次緩存清除操作,這樣就不用擔心緩存中的數據和數據庫中的數據不一致了。

那么這個延遲N秒,N是多大比較合適呢?一般來說,N要大于一次寫操作的時間,如果延遲時間小于寫入緩存的時間,會導致請求A 已經延遲清除了緩存,但是此時請求B緩存還未寫入,具體是多少,就要結合自己的業務來統計這個數值了。

通過訂閱MySQL binlog的方式處理緩存

上面講到的MQ處理方式需要業務代碼里面顯式地發送MQ消息。還有一種優雅的方式便是訂閱MySQL的binlog,監聽數據的真實變化情況以處理相關的緩存。

目前業界類似的產品有Canal,具體的操作圖如下:

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

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

相關文章

DA14585墨水屏學習

一、do_min_word void do_min_work(void) {timer_used_min app_easy_timer(APP_PERIPHERAL_CTRL_TIMER_DELAY_MINUTES, do_min_work);current_unix_time time_offset;time_offset 60;// if (isconnected 1)// {// GPIO_SetActive(GPIO_LED_PORT, GPIO_LED_PIN);// …

微服務調試問題總結

本地環境調試。 啟動本地微服務,使用公共nacos配置。利用如apifox進行本地代碼調試解決調試問題。除必要的業務微服務依賴包需要下載到本地。使用mvn clean install -DskipTests進行安裝啟動前選擇好profile環境進行啟動,啟動前記得mvn clean清理項目。…

C#學習第22天:網絡編程

網絡編程的核心概念 1. 套接字(Sockets) 定義:套接字是網絡通信的基本單元,提供了在網絡中進行數據交換的端點。用途:用于TCP/UDP網絡通信,支持低級別的網絡數據傳輸。 2.協議 TCP(Transmiss…

TWASandGWAS中GBS filtering and GWAS(1)

F:\文章代碼\TWASandGWAS\GBS filtering and GWAS README.TXT 請檢查幻燈片“Vitamaize_update_Gorelab_Ames_GBS_filtering_20191122.pptx”中關于阿姆斯(Ames)ID處理流程的詳細信息。 文件夾“Ames_ID_processing”包含了用于處理阿姆斯ID的文件和R…

圖像處理篇---opencv實現坐姿檢測

文章目錄 前言一、方法概述使用OpenCV和MediaPipe關鍵點檢測角度計算姿態評估 二、完整代碼實現三、代碼說明PostureDetector類find_pose()get_landmarks()cakculate_angle()evaluate_posture() 坐姿評估標準(可進行參數調整):可視化功能&…

.Net HttpClient 使用代理功能

HttpClient 使用代理功能 實際開發中,HttpClient 通過代理訪問目標服務器是常見的需求。 本文將全面介紹如何在 .NET 中配置 HttpClient 使用代理(Proxy)功能,包括基礎使用方式、代碼示例、以及與依賴注入結合的最佳實踐。 注意…

【學習路線】 游戲客戶端開發入門到進階

目錄 游戲客戶端開發入門到進階:系統學習路線與推薦書單一、學習總原則:從底層出發,項目驅動,持續迭代二、推薦學習路線圖(初學者→進階)第一階段:語言基礎與編程思維第二階段:游戲開…

精益數據分析(57/126):創業移情階段的核心要點與實踐方法

精益數據分析(57/126):創業移情階段的核心要點與實踐方法 在創業的浩瀚征程中,每一個階段都承載著獨特的使命與挑戰。今天,我們繼續秉持共同進步的理念,深入研讀《精益數據分析》,聚焦創業的首…

015枚舉之滑動窗口——算法備賽

滑動窗口 最大子數組和 題目描述 給你一個整數數組 nums &#xff0c;請你找出一個具有最大和的連續子數組&#xff08;子數組最少包含一個元素&#xff09;&#xff0c;返回其最大和。 原題鏈接 思路分析 見代碼注解 代碼 int maxSubArray(vector<int>& num…

微軟系統 紅帽系統 網絡故障排查:ping、traceroute、netstat

在微軟&#xff08;Windows&#xff09;和紅帽&#xff08;Red Hat Enterprise Linux&#xff0c;RHEL&#xff09;等系統中&#xff0c;網絡故障排查是確保系統正常運行的重要環節。 ping、traceroute&#xff08;在Windows中為tracert&#xff09;和netstat是三個常用的網絡…

解構認知邊界:論萬能方法的本體論批判與方法論重構——基于跨學科視閾的哲學-科學辯證

一、哲學維度的本體論批判 &#xff08;1&#xff09;理性主義的坍縮&#xff1a;從笛卡爾幻想到哥德爾陷阱 笛卡爾在《方法論》中構建的理性主義范式&#xff0c;企圖通過"普遍懷疑-數學演繹"雙重機制確立絕對方法體系。然而哥德爾不完備定理&#xff08;Gdel, 19…

【網絡入侵檢測】基于源碼分析Suricata的IP分片重組

【作者主頁】只道當時是尋常 【專欄介紹】Suricata入侵檢測。專注網絡、主機安全&#xff0c;歡迎關注與評論。 目錄 目錄 1.概要 2. 配置信息 2.1 名詞介紹 2.2 defrag 配置 3. 代碼實現 3.1 配置解析 3.1.1 defrag配置 3.1.2 主機系統策略 3.2 分片重組模塊 3.2.1…

二分查找的邊界問題

前言 二分查找(Binary Search)是一種高效的查找算法&#xff0c;時間復雜度為O(log n)。它適用于已排序的數組或列表。本文將詳細介紹二分查找的兩種常見寫法&#xff1a;閉區間寫法和左閉右開區間寫法。 一、二分查找基本思想 二分查找的核心思想是"分而治之"&am…

重慶醫科大學附屬第二醫院外科樓外擋墻自動化監測

1.項目概述 重慶醫科大學附屬第二醫院&#xff0c;重醫附二院&#xff0c;是集醫療、教學、科研、預防保健為一體的國家三級甲等綜合醫院。前身為始建于1892年的“重慶寬仁醫院”。醫院現有開放床位 1380張&#xff0c;年門診量超過百萬人次&#xff0c;年收治住院病人4.5萬人…

【Redis實戰篇】秒殺優化

1. 秒殺優化-異步秒殺思路 我們來回顧一下下單流程 當用戶發起請求&#xff0c;此時會請求nginx&#xff0c;nginx會訪問到tomcat&#xff0c;而tomcat中的程序&#xff0c;會進行串行操作&#xff0c;分成如下幾個步驟 1、查詢優惠卷 2、判斷秒殺庫存是否足夠 3、查詢訂單…

【idea】調試篇 idea調試技巧合集

前言&#xff1a;之前博主寫過一篇idea技巧合集的文章&#xff0c;由于技巧過于多了&#xff0c;文章很龐大&#xff0c;所以特地將調試相關的技巧單獨成章, 調試和我們日常開發是息息相關的&#xff0c;用好調試可以事半功倍 文章目錄 1. idea調試異步線程2. idea調試stream流…

postman 用法 LTS

postman 用法 LTS File ---- View ---- Show Postman Console

MySQL 數據庫故障排查指南

MySQL 數據庫故障排查指南 本指南旨在幫助您識別和解決常見的 MySQL 數據庫故障。我們將從問題識別開始&#xff0c;逐步深入到具體的故障類型和排查步驟。 1. 問題識別與信息收集 在開始排查之前&#xff0c;首先需要清晰地了解問題的現象和范圍。 故障現象&#xff1a; 數…

用AI寫簡歷是否可行?

讓AI批量寫簡歷然后投簡歷是絕對不行的&#xff01;&#xff01;&#xff01; 為什么不行&#xff0c;按照 "招聘經理" 工作經歷舉例&#xff1a; ai提示詞&#xff1a;請幫我寫一份招聘經理的工作經歷內容&#xff1a; 招聘經理 | XXX科技有限公司 | 2020年…

【從零實現JsonRpc框架#1】Json庫介紹

1.JsonCpp第三方庫 JSONCPP 是一個開源的 C 庫&#xff0c;用于解析和生成 JSON&#xff08;JavaScript Object Notation&#xff09;數據。它提供了簡單易用的接口&#xff0c;支持 JSON 的序列化和反序列化操作&#xff0c;適用于處理配置文件、網絡通信數據等場景。 2.Jso…