Redis的緩存一致性問題詳解

来源:https://www.cnblogs.com/pxypxy/archive/2023/03/13/17212828.html
-Advertisement-
Play Games

摘要:行存表示了一種數據的存儲方式,是最傳統的一種存儲方式。 本文分享自華為雲社區《【玩轉PB級數倉GaussDB(DWS)】行列存對比的一些事》,作者:sevenjiang。 行存表示了一種數據的存儲方式,是最傳統的一種存儲方式。對於GaussDB(DWS)來說可以認為其表示存儲引擎的基礎實現,在 ...


1、三種常用的緩存模式

1.旁路緩存模式

一般來說,如果允許緩存可以稍微的跟資料庫偶爾有不一致的情況,也就是說如果你的系統不是嚴格要求 “緩存+資料庫” 必須保持一致性的話,最好不要做這個方案,即:讀請求和寫請求串列化串到一個記憶體隊列里去。

採用緩存 + 資料庫讀寫的方式,就是 Cache Aside Pattern(旁路緩存模式)。

  • 讀的時候,先讀緩存,緩存沒有的話,就讀資料庫,然後取出數據後放入緩存,同時返迴響應。
  • 更新的時候,先更新資料庫,然後再刪除緩存

2.讀寫穿透模式

Read/Write Through Pattern 中服務端把 cache 視為主要數據存儲,從中讀取數據並將數據寫入其中。cache 服務負責將此數據讀取和寫入 db,從而減輕了應用程式的職責。

寫(Write Through):先查 cache,cache 中不存在,直接更新 db;cache 中存在,則先更新 cache,然後 cache 服務自己更新 db(同步更新 cache 和 db

讀(Read Through):從 cache 中讀取數據,讀取到就直接返回 ;讀取不到的話,先從 db 載入,寫入到 cache 後返迴響應。

Read-Through Pattern 實際只是在 Cache-Aside Pattern 之上進行了封裝。在 Cache-Aside Pattern 下,發生讀請求的時候,如果 cache 中不存在對應的數據,是由客戶端自己負責把數據寫入 cache,而 Read Through Pattern 則是 cache 服務自己來寫入緩存的,這對客戶端是透明的。和 Cache Aside Pattern 一樣, Read-Through Pattern 也有首次請求數據一定不再 cache 的問題,對於熱點數據可以提前放入緩存中。

3.非同步緩存寫入

非同步緩存寫入(Write Behind Pattern) 和 Read/Write Through Pattern 很相似,兩者都是由 cache 服務來負責 cache 和 db 的讀寫。

但是,兩個又有很大的不同:Read/Write Through 是同步更新 cache 和 db,而 Write Behind 則是只更新緩存,不直接更新 db,而是改為非同步批量的方式來更新 db。

很明顯,這種方式對數據一致性帶來了更大的挑戰,比如 cache 數據可能還沒非同步更新 db 的話,cache 服務可能就就掛掉了。

這種策略在我們平時開發過程中也非常非常少見,但是不代表它的應用場景少,比如消息隊列中消息的非同步寫入磁碟、MySQL 的 Innodb Buffer Pool 機制都用到了這種策略。

Write Behind Pattern 下 db 的寫性能非常高,非常適合一些數據經常變化又對數據一致性要求沒那麼高的場景,比如瀏覽量、點贊量。

2、緩存存在的問題?

1.為什麼先更新後刪除?

結論:無論先刪除還是先更新資料庫都存在數據一致性問題,那麼矮個子里選將軍,選個發生問題概率小的,就是先更新資料庫後刪除緩存。

先刪除緩存,再更新資料庫:如果刪除緩存失敗了,那麼會導致資料庫中是新數據,緩存中是舊數據,數據就出現了不一致。

2 個線程要併發「讀寫」數據,可能會發生以下場景:

  1. 線程 A 要更新 X = 2(原值 X = 1)
  2. 線程 A 先刪除緩存
  3. 線程 B 讀緩存,發現不存在,從資料庫中讀取到舊值(X = 1)
  4. 線程 A 將新值寫入資料庫(X = 2)
  5. 線程 B 將舊值寫入緩存(X = 1)

最終 X 的值在緩存中是 1(舊值),在資料庫中是 2(新值),發生不一致。

先更新資料庫,再刪除緩存:先刪除了緩存,然後要去修改資料庫,此時還沒修改。一個請求過來,去讀緩存,發現緩存空了,去查詢資料庫,查到了修改前的舊數據,並將其放到了緩存中。隨後數據變更的程式完成了資料庫的修改。資料庫和緩存中的數據不一樣了

  1. 緩存中 X 不存在(資料庫 X = 1)
  2. 線程 A 讀取資料庫,得到舊值(X = 1)
  3. 線程 B 更新資料庫(X = 2)
  4. 線程 B 刪除緩存
  5. 線程 A 將舊值寫入緩存(X = 1)

最終 X 的值在緩存中是 1(舊值),在主從庫中是 2(新值),也發生不一致。

這 2 個問題的核心在於:緩存都被回種了「舊值」

矮個子里選將軍

第二種方法其實概率很低,這是因為它必須滿足 3 個條件:

  1. 緩存剛好已失效
  2. 讀請求 + 寫請求併發
  3. 更新資料庫 + 刪除緩存的時間(步驟 3-4),要比讀資料庫 + 寫緩存時間短(步驟 2 和 5)

仔細想一下,條件 3 發生的概率其實是非常低的。

因為寫資料庫一般會先「加鎖」,所以更新資料庫,通常是要比讀資料庫的時間更長的,並且因為緩存的寫入速度是比資料庫的寫入速度快很多。這麼來看,「先更新資料庫 + 再刪除緩存」的方案,是可以保證數據一致性的。所以,我們應該採用這種方案,來操作資料庫和緩存。

2.解決方法

最有效的辦法就是,把緩存刪掉。但是,不能立即刪,而是需要「延遲刪」,即:緩存延遲雙刪策略

解決第一個問題:線上程 A 刪除緩存、更新完資料庫之後,先「休眠一會」,再「刪除」一次緩存。

解決第二個問題:線程 A 可以生成一條「延時消息」,寫到消息隊列中,消費者延時「刪除」緩存。

這兩個方案的目的,都是為了把緩存清掉,這樣一來,下次就可以從資料庫讀取到最新值,寫入緩存。

3.如何保證刪除緩存成功?

方案一:重試

首先想到的一個方案是:執行失敗後,重試。失敗後立即重試的問題在於:

  • 立即重試很大概率「還會失敗」
  • 「重試次數」設置多少才合理?
  • 重試會一直「占用」這個線程資源,無法服務其它客戶端請求

方案二:非同步重試

非同步重試其實就是:把重試請求扔到「消息隊列」中,然後由專門的消費者來重試,直到成功。把重試或第二步操作放到另一個服務中,這個服務用消息隊列來進行重試操作。

3、非同步重試方案-canal

我們的業務應用在修改數據時,「只需」修改資料庫,無需操作緩存。拿 MySQL 舉例,當一條數據發生修改時,MySQL 就會產生一條變更日誌(Binlog),我們可以訂閱這個日誌,拿到具體操作的數據,然後再根據這條數據,去刪除對應的緩存。訂閱變更日誌,目前也有了比較成熟的開源中間件,例如阿裡的 canal,使用這種方案的優點在於:

  • 無需考慮寫消息隊列失敗情況:只要寫 MySQL 成功,Binlog 肯定會有
  • 自動投遞到下游隊列:canal 自動把資料庫變更日誌「投遞」給下游的消息隊列

想要保證資料庫和緩存一致性,推薦採用「先更新資料庫,再刪除緩存」方案,並配合「消息隊列」或「訂閱變更日誌」的方式來做

圖片

參考: https://www.cnblogs.com/myseries/p/12068845.html

3種常用的緩存讀寫策略詳解

緩存和資料庫一致性問題,看這篇就夠了 - 水滴與銀彈


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 垃圾回收器 1.1. 對象可以在被需要時創建,不再使用時由JVM自動回收 1.2. GC是查找不再使用的對象,然後回收這些對象相關記憶體的過程 1.2.1. 找到不使用的對象、回收其記憶體、壓縮堆記憶體 1.3. 優化垃圾回收器比跟蹤指針引起的bug要容易得多(且耗時更少) 1.4. VM必須定期搜 ...
  • 簡介 Dapper是介於Entity framework與ADO的折中選擇。既滿足手寫查詢的高性能需求,又簡化了資料庫對象映射為記憶體對象的繁雜工作。Dapper.Contrib是對Dapper的進一步封裝,使對象的基本增刪改查等操作進一步簡化。 為什麼使用Dapper.Contrib 如果僅僅使用D ...
  • 在運行期間,我們可以使用 `Emit` 來組織一段 IL 代碼,進而動態生成一個方法,甚至是一個程式集(包括類型、方法或屬性等等)。這個過程我們稱之為動態編織。這一項技術應用比較廣泛,比如數據映射(Dapper)、動態代理(AOP)等等,目的是提升大量反射而產生的性能問題。 ...
  • Blazor Server,即運行在伺服器上的 Blazor 應用程式,它的優點是應用程式在首次運行時,客戶端不需要下載運行時。但它的代碼是在伺服器上執行的,然後通過 SignalR 通信來更新客戶端的 UI,所以它要求必須建立 Web Socket 連接。 用於 Blazor 應用的 Signal ...
  • 前言 假設你正在玩一款線上多人游戲,在游戲中,有多個角色需要進行不同的操作,例如攻擊、移動、釋放技能等等。 接下來,我們用玩游戲的例子,來解釋進程和和線程的概念,以及進程和線程的區別。 進程的基本概念 我們可以將整個游戲看作一個進程,它是操作系統中資源分配的基本單位,擁有自己的地址空間、記憶體、CPU ...
  • 1. ip命令 1.1. 摘要 ip是iproute2軟體包裡面的一個強大的網路配置工具,它能夠替代一些傳統的網路管理工具。例如:ifconfig、route等。這個手冊將分章節介紹ip命令及其選項。 1.2. ip命令的語法 ip命令的用法如下: [root@node01 ~]# ip Usage ...
  • 1 許可權基本介紹 drwxr-xr-x. 3 laffy snow 4096 3月 9 16:17 test 第1位:文件類型(d,-,l,c,b) d 文件夾 - 普通文件 l 軟鏈接 c 字元設備文件,如滑鼠鍵盤 b 塊設備,如硬碟 第2-4位:確定文件所有者對文件的許可權 第5-7位:確定文件所 ...
  • 在 WebAssembly 中使用 Rust 編寫 eBPF 程式併發布 OCI 鏡像 作者:於桐,鄭昱笙 eBPF(extended Berkeley Packet Filter)是一種高性能的內核虛擬機,可以運行在內核空間中,以收集系統和網路信息。隨著電腦技術的不斷發展,eBPF 的功能日益強 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...