MySQL與Redis如何保證雙寫一致性詳解
前言
首先,我們必須明確一個核心觀點:在分布式環(huán)境下,要實現(xiàn)強一致性(在任何時刻讀取的數(shù)據(jù)都是最新的)是極其困難且代價高昂的,通常會嚴(yán)重犧牲性能。因此,在實踐中,我們通常追求最終一致性,即允許在短暫的時間內(nèi)數(shù)據(jù)不一致,但通過一些手段保證數(shù)據(jù)最終會保持一致。
下面我將從基礎(chǔ)概念、各種策略、最佳實踐到最新方案,為你詳細(xì)講解。
一、基礎(chǔ)概念:為什么會有不一致?
在一個包含 MySQL(作為可靠數(shù)據(jù)源)和 Redis(作為緩存)的系統(tǒng)中,所有的寫操作(增、刪、改)都必須同時處理這兩個地方。

這個過程中,任何一步失敗或延遲都會導(dǎo)致不一致:
- 寫 MySQL 成功,寫 Redis 失敗:導(dǎo)致 Redis 中是舊數(shù)據(jù)。
- 寫 Redis 成功,寫 MySQL 失敗:導(dǎo)致 Redis 中是“臟數(shù)據(jù)”,數(shù)據(jù)庫中不存在。
- 并發(fā)讀寫:一個線程在更新數(shù)據(jù)庫,但還沒更新緩存時,另一個線程讀取了舊的緩存數(shù)據(jù)。
二、核心策略與模式
解決雙寫一致性有多種策略,我們需要根據(jù)業(yè)務(wù)場景(對一致性的要求、讀寫的比例等)進(jìn)行選擇。
策略一:Cache-Aside Pattern(旁路緩存模式)
這是最常用、最經(jīng)典的緩存模式。核心原則是:應(yīng)用程序直接與數(shù)據(jù)庫和緩存交互,緩存不作為寫入的必經(jīng)之路。
- 讀流程:
- 收到讀請求。
- 首先查詢 Redis,如果數(shù)據(jù)存在(緩存命中),直接返回。
- 如果 Redis 中沒有數(shù)據(jù)(緩存未命中),則從 MySQL 中查詢。
- 將從 MySQL 查詢到的數(shù)據(jù)寫入 Redis(以便后續(xù)讀?。缓蠓祷財?shù)據(jù)。
- 寫流程:
- 收到寫請求。
- 更新 MySQL 中的數(shù)據(jù)。
- 刪除 Redis 中對應(yīng)的緩存。
為什么是刪除(Invalidate)緩存,而不是更新緩存?
這是一個關(guān)鍵設(shè)計點!
- 性能:如果更新緩存,每次數(shù)據(jù)庫寫操作都要伴隨一次緩存寫操作,如果該數(shù)據(jù)并不經(jīng)常被讀取,那么這次緩存寫入就是浪費資源的。
- 并發(fā)安全:在并發(fā)寫場景下,更新緩存的順序可能與更新數(shù)據(jù)庫的順序不一致,導(dǎo)致緩存中是舊數(shù)據(jù)。而刪除操作是冪等的,更為安全。
Cache-Aside 如何保證一致性?
它通過“先更新數(shù)據(jù)庫,再刪除緩存”來盡力保證。但它依然存在不一致的窗口期:
- 線程 A 更新數(shù)據(jù)庫。
- 線程 B 讀取數(shù)據(jù),發(fā)現(xiàn)緩存不存在,從數(shù)據(jù)庫讀取舊數(shù)據(jù)(因為 A 還沒提交或剛提交)。
- 線程 B 將舊數(shù)據(jù)寫入緩存。
- 線程 A 刪除緩存。
這種情況發(fā)生的概率較低,因為通常數(shù)據(jù)庫寫操作(步驟1)會比讀操作(步驟2)耗時更長(因為涉及鎖、日志等),所以步驟2在步驟1之前完成的概率很小。但這是一種理論上的可能。
策略二:Write-Through / Read-Through Pattern(穿透讀寫模式)
在這種模式下,緩存層(或一個獨立的服務(wù))自己負(fù)責(zé)與數(shù)據(jù)庫交互。對應(yīng)用來說,它只與緩存交互。
- 寫流程:應(yīng)用寫入緩存,緩存組件同步地寫入數(shù)據(jù)庫。只有兩個都成功后才會返回成功。
- 讀流程:應(yīng)用讀取緩存,如果未命中,緩存組件自己從數(shù)據(jù)庫加載并填充緩存,然后返回。
優(yōu)點:邏輯對應(yīng)用透明,一致性比 Cache-Aside 更好。
缺點:性能較差,因為每次寫操作都必然涉及一次數(shù)據(jù)庫寫入。通常需要成熟的緩存中間件支持。
策略三:Write-Behind Pattern(異步寫回模式)
Write-Through 的異步版本。應(yīng)用寫入緩存后立即返回,緩存組件在之后某個時間點(例如攢夠一批數(shù)據(jù)或定時)批量異步地更新到數(shù)據(jù)庫。
優(yōu)點:寫性能極高。
缺點:有數(shù)據(jù)丟失風(fēng)險(緩存宕機),一致性最弱。適用于允許少量數(shù)據(jù)丟失的場景,如計數(shù)、點贊等。
三、保證最終一致性的進(jìn)階方案
為了彌補 Cache-Aside 模式中的缺陷,我們可以引入一些額外的機制。
方案一:延遲雙刪
針對 Cache-Aside 中提到的“先更新數(shù)據(jù)庫,再刪除緩存”可能帶來的并發(fā)問題,可以引入一個延遲刪除。
- 線程 A 更新數(shù)據(jù)庫。
- 線程 A 刪除緩存。
- 線程 A 休眠一個特定的時間(如 500ms - 1s)。
- 線程 A 再次刪除緩存。
第二次刪除是為了清理掉在第1次刪除后、其他線程可能寫入的舊數(shù)據(jù)。這個休眠時間需要根據(jù)業(yè)務(wù)讀寫耗時來估算。
優(yōu)點:簡單有效,能很大程度上解決并發(fā)讀寫導(dǎo)致的不一致。
缺點:降低了寫入吞吐量,休眠時間難以精確設(shè)定。
方案二:通過消息隊列異步刪除
為了解耦和重試,可以將刪除緩存的操作作為消息發(fā)送到消息隊列(如 RocketMQ, Kafka)。
- 更新數(shù)據(jù)庫。
- 向消息隊列發(fā)送一條刪除緩存的消息。
- 消費者消費該消息,執(zhí)行刪除 Redis 的操作。如果刪除失敗,消息會重試。
這保證了刪除緩存的操作至少會被執(zhí)行一次,大大提高了可靠性。
方案三:通過數(shù)據(jù)庫 Binlog 同步(最優(yōu)解)
這是目前最成熟、對業(yè)務(wù)侵入性最小、一致性最好的方案。其核心是利用 MySQL 的二進(jìn)制日志(Binlog)進(jìn)行增量數(shù)據(jù)同步。
工作原理:
- 業(yè)務(wù)系統(tǒng)正常寫入 MySQL。
- 由一個中間件(如 Canal, Debezium)偽裝成 MySQL 的從庫,訂閱 Binlog。
- 中間件解析 Binlog,獲取數(shù)據(jù)的變更詳情(增、刪、改)。
- 中間件根據(jù)變更,調(diào)用 Redis 的 API 來更新或刪除對應(yīng)的緩存。

優(yōu)點:
- 業(yè)務(wù)無侵入:業(yè)務(wù)代碼只關(guān)心寫數(shù)據(jù)庫,完全不知道緩存的存在。
- 高性能:數(shù)據(jù)庫和緩存的同步是異步的,不影響主業(yè)務(wù)鏈路的性能。
- 強保證:由于基于 Binlog,它能保證只要數(shù)據(jù)庫變了,緩存最終一定會被同步。順序也與數(shù)據(jù)庫一致。
缺點:
- 架構(gòu)復(fù)雜,需要維護(hù)額外的同步組件。
- 同步有毫秒級到秒級的延遲。
四、總結(jié)與最佳實踐選擇
策略 | 一致性保證 | 性能 | 復(fù)雜度 | 適用場景 |
Cache-Aside + 刪除 | 最終一致性(有微弱不一致風(fēng)險) | 高 | 低 | 絕大多數(shù)場景的首選,讀多寫少 |
Cache-Aside + 延遲雙刪 | 更好的最終一致性 | 中 | 低 | 對一致性要求稍高,且能接受一定延遲的寫操作 |
Write-Through | 強一致性 | 中 | 中 | 寫多讀少,且對一致性要求非常高的場景 |
Binlog 同步 | 最終一致性(推薦) | 高 | 高 | 大型、高要求項目的最佳實踐,對業(yè)務(wù)無侵入 |
通用建議:
- 首選方案:對于大多數(shù)應(yīng)用,從 Cache-Aside(先更新數(shù)據(jù)庫,再刪除緩存) 開始。它簡單、有效,在大多數(shù)情況下已經(jīng)足夠。
- 進(jìn)階保障:如果 Cache-Aside 的不一致窗口無法接受,可以引入延遲雙刪或消息隊列異步刪除來增強。
- 終極方案:當(dāng)業(yè)務(wù)發(fā)展到一定規(guī)模,對一致性和系統(tǒng)解耦有更高要求時,投入資源搭建基于 Binlog 的異步同步方案。這是業(yè)界證明最可靠的方案。
- 設(shè)置合理的過期時間:無論如何,都給 Redis 中的緩存設(shè)置一個過期時間(TTL)。這是一個安全網(wǎng),即使同步邏輯出現(xiàn)問題,舊數(shù)據(jù)也會自動失效,最終從數(shù)據(jù)庫加載新數(shù)據(jù),保證最終一致性。
- 業(yè)務(wù)容忍度:最重要的是,與產(chǎn)品經(jīng)理確認(rèn)業(yè)務(wù)對一致性的容忍度。很多時候,1-2秒內(nèi)的數(shù)據(jù)不一致用戶是感知不到的,不需要為此付出巨大的架構(gòu)和性能代價。
總結(jié)
到此這篇關(guān)于MySQL與Redis如何保證雙寫一致性的文章就介紹到這了,更多相關(guān)MySQL與Redis保證雙寫一致性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
簡單了解操作mysql數(shù)據(jù)庫的命令行神器mycli
這篇文章主要介紹了簡單了解操作mysql數(shù)據(jù)庫的命令行神器mycli,今天發(fā)現(xiàn)一個操作數(shù)據(jù)庫的命令行工具,可以自動補全和語法高亮,,需要的朋友可以參考下2019-06-06
MySQL二進(jìn)制日志(bin_log)的作用與使用方法
BinLog又稱為二進(jìn)制日志,是MySQL服務(wù)層的數(shù)據(jù)日志,MySQL所有的存儲引擎都支持BinLog,這篇文章主要給大家介紹了關(guān)于MySQL二進(jìn)制日志(bin_log)的作用與使用方法的相關(guān)資料,需要的朋友可以參考下2024-06-06
解讀sql中timestamp和datetime之間的轉(zhuǎn)換
這篇文章主要介紹了解讀sql中timestamp和datetime之間的轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12
關(guān)于MySQL的存儲函數(shù)(自定義函數(shù))的定義和使用方法詳解
本文主要講解了關(guān)于MySQL的存儲函數(shù)(自定義函數(shù))的定義和使用方法詳解與存儲函數(shù)與存儲過程的區(qū)別2018-03-03

