自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何保證Redis與MySQL雙寫(xiě)一致性?連續(xù)兩個(gè)面試問(wèn)到了!

數(shù)據(jù)庫(kù) MySQL
對(duì)于高并發(fā)環(huán)境,可能需要結(jié)合分布式鎖、消息隊(duì)列或緩存失效延時(shí)等技術(shù),進(jìn)一步確保并發(fā)寫(xiě)操作下的數(shù)據(jù)一致性。異步處理binlog時(shí),務(wù)必考慮異常處理機(jī)制和重試策略,確保binlog事件能夠正確處理并執(zhí)行緩存更新操作。

引言

Redis作為一款高效的內(nèi)存數(shù)據(jù)存儲(chǔ)系統(tǒng),憑借其優(yōu)異的讀寫(xiě)性能和豐富的數(shù)據(jù)結(jié)構(gòu)支持,被廣泛應(yīng)用于緩存層以提升整個(gè)系統(tǒng)的響應(yīng)速度和吞吐量。尤其是在與關(guān)系型數(shù)據(jù)庫(kù)(如MySQL、PostgreSQL等)結(jié)合使用時(shí),通過(guò)將熱點(diǎn)數(shù)據(jù)存儲(chǔ)在Redis中,可以在很大程度上緩解數(shù)據(jù)庫(kù)的壓力,提高整體系統(tǒng)的性能表現(xiàn)。

然而,在這種架構(gòu)中,一個(gè)不容忽視的問(wèn)題就是如何確保Redis緩存與數(shù)據(jù)庫(kù)之間的雙寫(xiě)一致性。所謂雙寫(xiě)一致性,是指當(dāng)數(shù)據(jù)在數(shù)據(jù)庫(kù)中發(fā)生變更時(shí),能夠及時(shí)且準(zhǔn)確地反映在Redis緩存中,反之亦然,以避免出現(xiàn)因緩存與數(shù)據(jù)庫(kù)數(shù)據(jù)不一致導(dǎo)致的業(yè)務(wù)邏輯錯(cuò)誤或用戶(hù)體驗(yàn)下降。尤其在高并發(fā)場(chǎng)景下,由于網(wǎng)絡(luò)延遲、并發(fā)控制等因素,保證雙寫(xiě)一致性變得更加復(fù)雜。

在實(shí)際業(yè)務(wù)開(kāi)發(fā)中,若不能妥善處理好緩存與數(shù)據(jù)庫(kù)的雙寫(xiě)一致性問(wèn)題,可能會(huì)帶來(lái)諸如數(shù)據(jù)丟失、臟讀、重復(fù)讀等一系列嚴(yán)重影響系統(tǒng)穩(wěn)定性和可靠性的后果。本文將嘗試剖析這一問(wèn)題,介紹日常開(kāi)發(fā)中常用的一些策略和模式,并結(jié)合具體場(chǎng)景分析不同的解決方案,為大家提供一些有力的技術(shù)參考和支持。

圖片圖片

談?wù)劮植际较到y(tǒng)中的一致性

分布式系統(tǒng)中的一致性指的是在多個(gè)節(jié)點(diǎn)上存儲(chǔ)和處理數(shù)據(jù)時(shí),確保系統(tǒng)中的數(shù)據(jù)在不同節(jié)點(diǎn)之間保持一致的特性。在分布式系統(tǒng)中,一致性通??梢苑譃橐韵聨讉€(gè)類(lèi)別:

1. 強(qiáng)一致性:所有節(jié)點(diǎn)在任何時(shí)間都看到相同的數(shù)據(jù)。任何更新操作都會(huì)立即對(duì)所有節(jié)點(diǎn)可見(jiàn),保證了數(shù)據(jù)的強(qiáng)一致性。這意味著,如果一個(gè)節(jié)點(diǎn)完成了寫(xiě)操作,那么所有其他節(jié)點(diǎn)讀取相同的數(shù)據(jù)之后,都將看到最新的結(jié)果。強(qiáng)一致性通常需要付出更高的代價(jià),例如增加通信開(kāi)銷(xiāo)和降低系統(tǒng)的可用性。

2. 弱一致性: 系統(tǒng)中的數(shù)據(jù)在某些情況下可能會(huì)出現(xiàn)不一致的狀態(tài),但最終會(huì)收斂到一致?tīng)顟B(tài)。弱一致性下的系統(tǒng)允許在一段時(shí)間內(nèi),不同節(jié)點(diǎn)之間看到不同的數(shù)據(jù)狀態(tài)。弱一致性通常用于需要在性能和一致性之間進(jìn)行權(quán)衡的場(chǎng)景,例如緩存系統(tǒng)等。

3. 最終一致性: 是弱一致性的一種特例,它保證了在經(jīng)過(guò)一段時(shí)間后,系統(tǒng)中的所有節(jié)點(diǎn)最終都會(huì)達(dá)到一致?tīng)顟B(tài)。盡管在數(shù)據(jù)更新時(shí)可能會(huì)出現(xiàn)一段時(shí)間的不一致,但最終數(shù)據(jù)會(huì)收斂到一致?tīng)顟B(tài)。最終一致性通常通過(guò)一些技術(shù)手段來(lái)實(shí)現(xiàn),例如基于版本向量或時(shí)間戳的數(shù)據(jù)復(fù)制和同步機(jī)制。

除此之外,還有一些其他的一致性類(lèi)別,例如:因果一致性,順序一致性,基于本篇文章討論的重點(diǎn),我們暫且只討論以上三種一致性類(lèi)別。

什么是雙寫(xiě)一致性問(wèn)題?

在分布式系統(tǒng)中,雙寫(xiě)一致性主要指在一個(gè)數(shù)據(jù)同時(shí)存在于緩存(如Redis)和持久化存儲(chǔ)(如數(shù)據(jù)庫(kù))的情況下,任何一方的數(shù)據(jù)更新都必須確保另一方數(shù)據(jù)的同步更新,以保持雙方數(shù)據(jù)的一致?tīng)顟B(tài)。這一問(wèn)題的核心在于如何在并發(fā)環(huán)境下正確處理緩存與數(shù)據(jù)庫(kù)的讀寫(xiě)交互,防止數(shù)據(jù)出現(xiàn)不一致的情況。

典型場(chǎng)景分析

1. 寫(xiě)數(shù)據(jù)庫(kù)后忘記更新緩存:當(dāng)直接對(duì)數(shù)據(jù)庫(kù)進(jìn)行更新操作而沒(méi)有相應(yīng)地更新緩存時(shí),后續(xù)的讀請(qǐng)求可能仍然從緩存中獲取舊數(shù)據(jù),導(dǎo)致數(shù)據(jù)的不一致。

2. 刪除緩存后數(shù)據(jù)庫(kù)更新失?。?nbsp;在某些場(chǎng)景下,為了保證數(shù)據(jù)新鮮度,會(huì)在更新數(shù)據(jù)庫(kù)前先刪除緩存。但如果數(shù)據(jù)庫(kù)更新過(guò)程中出現(xiàn)異常導(dǎo)致更新失敗,那么緩存將長(zhǎng)時(shí)間處于空缺狀態(tài),新的查詢(xún)將會(huì)直接命中數(shù)據(jù)庫(kù),加重?cái)?shù)據(jù)庫(kù)壓力,并可能導(dǎo)致數(shù)據(jù)版本混亂。

3. 并發(fā)環(huán)境下讀寫(xiě)操作的交錯(cuò)執(zhí)行:在高并發(fā)場(chǎng)景下,可能存在多個(gè)讀寫(xiě)請(qǐng)求同時(shí)操作同一份數(shù)據(jù)的情況。比如,在刪除緩存、寫(xiě)入數(shù)據(jù)庫(kù)的過(guò)程中,新的讀請(qǐng)求獲取到了舊的數(shù)據(jù)庫(kù)數(shù)據(jù)并放入緩存,此時(shí)就出現(xiàn)了數(shù)據(jù)不一致的現(xiàn)象。

4. 主從復(fù)制延遲與緩存失效時(shí)間窗口沖突:對(duì)于具備主從復(fù)制功能的數(shù)據(jù)庫(kù)集群,主庫(kù)更新數(shù)據(jù)后,存在一定的延遲才將數(shù)據(jù)同步到從庫(kù)。如果在此期間緩存剛好過(guò)期并重新從數(shù)據(jù)庫(kù)加載數(shù)據(jù),可能會(huì)從尚未完成同步的從庫(kù)讀取到舊數(shù)據(jù),進(jìn)而導(dǎo)致緩存與主庫(kù)數(shù)據(jù)的不一致。

數(shù)據(jù)不一致不僅會(huì)導(dǎo)致業(yè)務(wù)邏輯出錯(cuò),還可能引發(fā)用戶(hù)界面展示錯(cuò)誤、交易狀態(tài)不準(zhǔn)確等問(wèn)題,嚴(yán)重時(shí)甚至?xí)绊懴到y(tǒng)的正常運(yùn)行和用戶(hù)體驗(yàn)。

解決雙寫(xiě)一致性問(wèn)題的主要策略

在解決Redis緩存與數(shù)據(jù)庫(kù)雙寫(xiě)一致性問(wèn)題上,有多種策略和模式。我們主要介紹以下幾種主要的策略:

Cache Aside Pattern(旁路緩存模式)

Cache Aside Pattern 是一種在分布式系統(tǒng)中廣泛采用的緩存和數(shù)據(jù)庫(kù)協(xié)同工作策略,在這個(gè)模式中,數(shù)據(jù)以數(shù)據(jù)庫(kù)為主存儲(chǔ),緩存作為提升讀取效率的輔助手段。也是日常中比較常見(jiàn)的一種手段。其工作流程如下:

圖片圖片

由上圖我們可以看出Cache Aside Pattern的工作原理:

? 讀取操作:首先嘗試從緩存中獲取數(shù)據(jù),如果緩存命中,則直接返回;否則,從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)并將其放入緩存,最后返回給客戶(hù)端。

? 更新操作:當(dāng)需要更新數(shù)據(jù)時(shí),首先更新數(shù)據(jù)庫(kù),然后再清除或使緩存中的對(duì)應(yīng)數(shù)據(jù)失效。這樣一來(lái),后續(xù)的讀請(qǐng)求將無(wú)法從緩存獲取數(shù)據(jù),從而迫使系統(tǒng)從數(shù)據(jù)庫(kù)加載最新的數(shù)據(jù)并重新填充緩存。

我們從更新操作上看會(huì)發(fā)現(xiàn)兩個(gè)很有意思的問(wèn)題:

為什么操作緩存的時(shí)候是刪除舊緩存而不是直接更新緩存?

我們舉例模擬下并發(fā)環(huán)境下的更新DB&緩存:

? 線(xiàn)程A先發(fā)起一個(gè)寫(xiě)操作,第一步先更新數(shù)據(jù)庫(kù),然后更新緩存

? 線(xiàn)程B再發(fā)起一個(gè)寫(xiě)操作,第二步更新了數(shù)據(jù)庫(kù),然后更新緩存 當(dāng)以上兩個(gè)線(xiàn)程的執(zhí)行,如果嚴(yán)格先后順序執(zhí)行,那么對(duì)于更新緩存還是刪除緩存去操作緩存都可以,但是如果兩個(gè)線(xiàn)程同時(shí)執(zhí)行時(shí),由于網(wǎng)絡(luò)或者其他原因,導(dǎo)致線(xiàn)程B先執(zhí)行完更新緩存,然后線(xiàn)程A才會(huì)更新緩存。如下圖:

圖片圖片

這時(shí)候緩存中保存的就是線(xiàn)程A的數(shù)據(jù),而數(shù)據(jù)庫(kù)中保存的是線(xiàn)程B的數(shù)據(jù)。這時(shí)候如果讀取到的緩存就是臟數(shù)據(jù)。但是如果使用刪除緩存取代更新緩存,那么就不會(huì)出現(xiàn)這個(gè)臟數(shù)據(jù)。這種方式可以簡(jiǎn)化并發(fā)控制、保證數(shù)據(jù)一致性、降低操作復(fù)雜度,并能更好地適應(yīng)各種潛在的異常場(chǎng)景和緩存策略。盡管這種方法可能會(huì)增加一次數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)的成本,但在實(shí)際應(yīng)用中,考慮到數(shù)據(jù)的一致性和系統(tǒng)的健壯性,這是值得付出的折衷。

并且在寫(xiě)多讀少的情況下,數(shù)據(jù)很多時(shí)候并不會(huì)被讀取到,但是一直被頻繁的更新,這樣也會(huì)浪費(fèi)性能。實(shí)際上,寫(xiě)多的場(chǎng)景,用緩存也不是很劃算。只有在讀多寫(xiě)少的情況下使用緩存才會(huì)發(fā)揮更大的價(jià)值。

為什么是先操作數(shù)據(jù)庫(kù)再操作緩存?

在操作緩存時(shí),為什么要先操作數(shù)據(jù)庫(kù)而不是先操作緩存?我們同樣舉例模擬兩個(gè)線(xiàn)程,線(xiàn)程A寫(xiě)入數(shù)據(jù),先刪除緩存在更新DB,線(xiàn)程B讀取數(shù)據(jù)。流程如下:

1. 線(xiàn)程A發(fā)起一個(gè)寫(xiě)操作,第一步刪除緩存

2. 此時(shí)線(xiàn)程B發(fā)起一個(gè)讀操作,緩存中沒(méi)有,則繼續(xù)讀DB,讀出來(lái)一個(gè)老數(shù)據(jù)

3. 然后線(xiàn)程B把老數(shù)據(jù)放入緩存中

4. 線(xiàn)程A更新DB數(shù)據(jù)

圖片圖片

所以這樣就會(huì)出現(xiàn)緩存中存儲(chǔ)的是舊數(shù)據(jù),而數(shù)據(jù)庫(kù)中存儲(chǔ)的是新數(shù)據(jù),這樣就出現(xiàn)臟數(shù)據(jù),所以我們一般都采取先操作數(shù)據(jù)庫(kù),在操作緩存。這樣后續(xù)的讀請(qǐng)求從數(shù)據(jù)庫(kù)獲取最新數(shù)據(jù)并重新填充緩存。這樣的設(shè)計(jì)降低了數(shù)據(jù)不一致的風(fēng)險(xiǎn),提升了系統(tǒng)的可靠性。同時(shí),這也符合CAP定理中對(duì)于一致性(Consistency)和可用性(Availability)權(quán)衡的要求,在很多場(chǎng)景下,數(shù)據(jù)一致性被優(yōu)先考慮。

Cache Aside Pattern相對(duì)簡(jiǎn)單直觀,容易理解和實(shí)現(xiàn)。只需要簡(jiǎn)單的判斷和緩存失效邏輯即可,對(duì)已有系統(tǒng)的改動(dòng)較小。并且由于緩存是按需加載的,所以不會(huì)浪費(fèi)寶貴的緩存空間存儲(chǔ)未被訪(fǎng)問(wèn)的數(shù)據(jù),同時(shí)我們可以根據(jù)實(shí)際情況決定何時(shí)加載和清理緩存。

盡管Cache Aside Pattern在大多數(shù)情況下可以保證最終一致性,但它并不能保證強(qiáng)一致性。在數(shù)據(jù)庫(kù)更新后的短暫時(shí)間內(nèi)(還未開(kāi)始操作緩存),如果有讀請(qǐng)求發(fā)生,緩存中仍是舊數(shù)據(jù),但是實(shí)際數(shù)據(jù)庫(kù)中已是最新數(shù)據(jù),造成短暫的數(shù)據(jù)不一致。在并發(fā)環(huán)境下,特別是在更新操作時(shí),有可能在更新數(shù)據(jù)庫(kù)和刪除緩存之間的時(shí)間窗口內(nèi),新的讀請(qǐng)求加載了舊數(shù)據(jù)到緩存,導(dǎo)致不一致。

Read-Through/Write-Through(讀寫(xiě)穿透)

Read-Through 和 Write-Through 是兩種與緩存相關(guān)的策略,它們主要用于緩存系統(tǒng)與持久化存儲(chǔ)之間的數(shù)據(jù)交互,旨在確保緩存與底層數(shù)據(jù)存儲(chǔ)的一致性。

Read-Through(讀穿透)

Read-Through 是一種在緩存中找不到數(shù)據(jù)時(shí),自動(dòng)從持久化存儲(chǔ)中加載數(shù)據(jù)并回填到緩存中的策略。具體執(zhí)行流程如下:

  • ? 客戶(hù)端發(fā)起讀請(qǐng)求到緩存系統(tǒng)。
  • ? 緩存系統(tǒng)檢查是否存在請(qǐng)求的數(shù)據(jù)。
  • ? 如果數(shù)據(jù)不在緩存中,緩存系統(tǒng)會(huì)透明地向底層數(shù)據(jù)存儲(chǔ)(如數(shù)據(jù)庫(kù))發(fā)起讀請(qǐng)求。
  • ? 數(shù)據(jù)庫(kù)返回?cái)?shù)據(jù)后,緩存系統(tǒng)將數(shù)據(jù)存儲(chǔ)到緩存中,并將數(shù)據(jù)返回給客戶(hù)端。
  • ? 下次同樣的讀請(qǐng)求就可以直接從緩存中獲取數(shù)據(jù),提高了讀取效率。

圖片圖片

整體簡(jiǎn)要流程類(lèi)似Cache Aside Pattern,但在緩存未命中的情況下,Read-Through 策略會(huì)自動(dòng)隱式地從數(shù)據(jù)庫(kù)加載數(shù)據(jù)并填充到緩存中,而無(wú)需應(yīng)用程序顯式地進(jìn)行數(shù)據(jù)庫(kù)查詢(xún)。

Cache Aside Pattern 更多地依賴(lài)于應(yīng)用程序自己來(lái)管理緩存與數(shù)據(jù)庫(kù)之間的數(shù)據(jù)流動(dòng),包括緩存填充、失效和更新。而Read-Through Pattern 則是在緩存系統(tǒng)內(nèi)部實(shí)現(xiàn)了一個(gè)更加自動(dòng)化的過(guò)程,使得應(yīng)用程序無(wú)需關(guān)心數(shù)據(jù)是從緩存還是數(shù)據(jù)庫(kù)中獲取,以及如何保持兩者的一致性。在Read-Through 中,緩存系統(tǒng)承擔(dān)了更多的職責(zé),實(shí)現(xiàn)了更緊密的緩存與數(shù)據(jù)庫(kù)集成,從而簡(jiǎn)化了應(yīng)用程序的設(shè)計(jì)和實(shí)現(xiàn)。

Write-Through(寫(xiě)穿透)

Write-Through 是一種在緩存中更新數(shù)據(jù)時(shí),同時(shí)將更新操作同步到持久化存儲(chǔ)的策略。具體流程如下:

? 當(dāng)客戶(hù)端向緩存系統(tǒng)發(fā)出寫(xiě)請(qǐng)求時(shí),緩存系統(tǒng)首先更新緩存中的數(shù)據(jù)。

? 同時(shí),緩存系統(tǒng)還會(huì)把這次更新操作同步到底層數(shù)據(jù)存儲(chǔ)(如數(shù)據(jù)庫(kù))。

? 當(dāng)數(shù)據(jù)在數(shù)據(jù)庫(kù)中成功更新后,整個(gè)寫(xiě)操作才算完成。

? 這樣,無(wú)論是從緩存還是直接從數(shù)據(jù)庫(kù)讀取,都能得到最新一致的數(shù)據(jù)。

圖片圖片

Read-Through 和 Write-Through 的共同目標(biāo)是確保緩存與底層數(shù)據(jù)存儲(chǔ)之間的一致性,并通過(guò)自動(dòng)化的方式隱藏了緩存與持久化存儲(chǔ)之間的交互細(xì)節(jié),簡(jiǎn)化了客戶(hù)端的處理邏輯。這兩種策略經(jīng)常一起使用,以提供無(wú)縫且一致的數(shù)據(jù)訪(fǎng)問(wèn)體驗(yàn),特別適用于那些對(duì)數(shù)據(jù)一致性要求較高的應(yīng)用場(chǎng)景。然而,需要注意的是,雖然它們有助于提高數(shù)據(jù)一致性,但在高并發(fā)或網(wǎng)絡(luò)不穩(wěn)定的情況下,仍然需要考慮并發(fā)控制和事務(wù)處理等問(wèn)題,以防止數(shù)據(jù)不一致的情況發(fā)生。

Write behind (異步緩存寫(xiě)入)

Write Behind(異步緩存寫(xiě)入),也稱(chēng)為 Write Back(回寫(xiě))或 異步更新策略,是一種在處理緩存與持久化存儲(chǔ)(如數(shù)據(jù)庫(kù))之間數(shù)據(jù)同步時(shí)的策略。在這種模式下,當(dāng)數(shù)據(jù)在緩存中被更新時(shí),并非立即同步更新到數(shù)據(jù)庫(kù),而是將更新操作暫存起來(lái),隨后以異步的方式批量地將緩存中的更改寫(xiě)入持久化存儲(chǔ)。其流程如下:

? 應(yīng)用程序首先在緩存中執(zhí)行數(shù)據(jù)更新操作,而不是直接更新數(shù)據(jù)庫(kù)。

? 緩存系統(tǒng)會(huì)將此次更新操作記錄下來(lái),暫存于一個(gè)隊(duì)列(如日志文件或內(nèi)存隊(duì)列)中,而不是立刻同步到數(shù)據(jù)庫(kù)。

? 在后臺(tái)有一個(gè)獨(dú)立的進(jìn)程或線(xiàn)程定期(或者當(dāng)隊(duì)列積累到一定大小時(shí))從暫存隊(duì)列中取出更新操作,然后批量地將這些更改寫(xiě)入數(shù)據(jù)庫(kù)。

圖片圖片

使用 Write Behind 策略時(shí),由于更新并非即時(shí)同步到數(shù)據(jù)庫(kù),所以在異步處理完成之前,如果緩存或系統(tǒng)出現(xiàn)故障,可能會(huì)丟失部分更新操作。并且對(duì)于高度敏感且要求強(qiáng)一致性的數(shù)據(jù),Write Behind 策略并不適用,因?yàn)樗鼰o(wú)法提供嚴(yán)格的事務(wù)性和實(shí)時(shí)一致性保證。Write Behind 適用于那些可以容忍一定延遲的數(shù)據(jù)一致性場(chǎng)景,通過(guò)犧牲一定程度的一致性換取更高的系統(tǒng)性能和擴(kuò)展性。

解決雙寫(xiě)一致性問(wèn)題的3種方案

以上我們主要講解了解決雙寫(xiě)一致性問(wèn)題的主要策略,但是每種策略都有一定的局限性,所以我們?cè)趯?shí)際運(yùn)用中,還要結(jié)合一些其他策略去屏蔽上述策略的缺點(diǎn)。

1. 延時(shí)雙刪策略

延時(shí)雙刪策略主要用于解決在高并發(fā)場(chǎng)景下,由于網(wǎng)絡(luò)延遲、并發(fā)控制等原因造成的數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)不一致的問(wèn)題。

當(dāng)更新數(shù)據(jù)庫(kù)時(shí),首先刪除對(duì)應(yīng)的緩存項(xiàng),以確保后續(xù)的讀請(qǐng)求會(huì)從數(shù)據(jù)庫(kù)加載最新數(shù)據(jù)。但是由于網(wǎng)絡(luò)延遲或其他不確定性因素,刪除緩存與數(shù)據(jù)庫(kù)更新之間可能存在時(shí)間窗口,導(dǎo)致在這段時(shí)間內(nèi)的讀請(qǐng)求從數(shù)據(jù)庫(kù)讀取數(shù)據(jù)后寫(xiě)回緩存,新寫(xiě)入的緩存數(shù)據(jù)可能還未反映出數(shù)據(jù)庫(kù)的最新變更。

所以為了解決這個(gè)問(wèn)題,延時(shí)雙刪策略在第一次刪除緩存后,設(shè)定一段短暫的延遲時(shí)間,如幾百毫秒,然后在這段延遲時(shí)間結(jié)束后再次嘗試刪除緩存。這樣做的目的是確保在數(shù)據(jù)庫(kù)更新傳播到所有節(jié)點(diǎn),并且在緩存中的舊數(shù)據(jù)徹底過(guò)期失效之前,第二次刪除操作可以消除緩存中可能存在的舊數(shù)據(jù),從而提高數(shù)據(jù)一致性。

public class DelayDoubleDeleteService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private TaskScheduler taskScheduler;

    public void updateAndScheduleDoubleDelete(String key, String value) {
        // 更新數(shù)據(jù)庫(kù)...
        updateDatabase(key, value);

        // 刪除緩存
        redisTemplate.delete(key);

        // 延遲執(zhí)行第二次刪除
        taskScheduler.schedule(() -> {
            redisTemplate.delete(key);
        }, new CronTrigger("0/1 * * * * ?")); // 假設(shè)1秒后執(zhí)行,實(shí)際應(yīng)根據(jù)需求設(shè)置定時(shí)表達(dá)式
    }

    // 更新數(shù)據(jù)庫(kù)的邏輯
    private void updateDatabase(String key, String value) {
        
    }
}

這種方式可以較好地處理網(wǎng)絡(luò)延遲導(dǎo)致的數(shù)據(jù)不一致問(wèn)題,較少的并發(fā)寫(xiě)入數(shù)據(jù)庫(kù)和緩存,降低系統(tǒng)的壓力。但是,延遲時(shí)間的選擇需要權(quán)衡,過(guò)短可能導(dǎo)致實(shí)際效果不明顯,過(guò)長(zhǎng)可能影響用戶(hù)體驗(yàn)。并且對(duì)于極端并發(fā)場(chǎng)景,仍可能存在數(shù)據(jù)不一致的風(fēng)險(xiǎn)。

2. 刪除緩存重試機(jī)制

刪除緩存重試機(jī)制是在刪除緩存操作失敗時(shí),設(shè)定一個(gè)重試策略,確保緩存最終能被正確刪除,以維持與數(shù)據(jù)庫(kù)的一致性。

在執(zhí)行數(shù)據(jù)庫(kù)更新操作后,嘗試刪除關(guān)聯(lián)的緩存項(xiàng)。如果首次刪除緩存失敗(例如網(wǎng)絡(luò)波動(dòng)、緩存服務(wù)暫時(shí)不可用等情況),系統(tǒng)進(jìn)入重試邏輯,按照預(yù)先設(shè)定的策略(如指數(shù)退避、固定間隔重試等)進(jìn)行多次嘗試。直到緩存刪除成功,或者達(dá)到最大重試次數(shù)為止。通過(guò)這種方式,即使在異常情況下也能盡量保證緩存與數(shù)據(jù)庫(kù)的一致性。

@Service
public class RetryableCacheService {

    @Autowired
    private CacheManager cacheManager;

    @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000L))
    public void deleteCacheWithRetry(String key) {
        ((org.springframework.data.redis.cache.RedisCacheManager) cacheManager).getCache("myCache").evict(key);
    }

    public void updateAndDeleteCache(String key, String value) {
        // 更新數(shù)據(jù)庫(kù)...
        updateDatabase(key, value);

        // 嘗試刪除緩存,失敗時(shí)自動(dòng)重試
        deleteCacheWithRetry(key);
    }

    // 更新數(shù)據(jù)庫(kù)的邏輯,此處僅示意
    private void updateDatabase(String key, String value) {
        // ...
    }
}

這種重試方式確保緩存刪除操作的成功執(zhí)行,可以應(yīng)對(duì)網(wǎng)絡(luò)抖動(dòng)等導(dǎo)致的臨時(shí)性錯(cuò)誤,提高數(shù)據(jù)一致性。但是可能占用額外的系統(tǒng)資源和時(shí)間,重試次數(shù)過(guò)多可能會(huì)阻塞其他操作。

監(jiān)聽(tīng)并讀取biglog異步刪除緩存

在數(shù)據(jù)庫(kù)發(fā)生寫(xiě)操作時(shí),將變更記錄在binlog或類(lèi)似的事務(wù)日志中,然后使用一個(gè)專(zhuān)門(mén)的異步服務(wù)或者監(jiān)聽(tīng)器訂閱binlog的變化(比如Canal),一旦檢測(cè)到有數(shù)據(jù)更新,便根據(jù)binlog中的操作信息定位到受影響的緩存項(xiàng)。講這些需要更新緩存的數(shù)據(jù)發(fā)送到消息隊(duì)列,消費(fèi)者處理消息隊(duì)列中的事件,異步地刪除或更新緩存中的對(duì)應(yīng)數(shù)據(jù),確保緩存與數(shù)據(jù)庫(kù)保持一致。

@Service
public class BinlogEventHandler {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    public void handleBinlogEvent(BinlogEvent binlogEvent) {
        // 解析binlogEvent,獲取需要更新緩存的key
        String cacheKey = deriveCacheKeyFromBinlogEvent(binlogEvent);

        // 發(fā)送到RocketMQ
        rocketMQTemplate.asyncSend("cacheUpdateTopic", cacheKey, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                // 發(fā)送成功處理
            }

            @Override
            public void onException(Throwable e) {
                // 發(fā)送失敗處理
            }
        });
    }

    // 從binlog事件中獲取緩存key的邏輯,這里僅為示意
    private String deriveCacheKeyFromBinlogEvent(BinlogEvent binlogEvent) {
        // ...
    }
}

@RocketMQMessageListener(consumerGroup = "myConsumerGroup", topic = "cacheUpdateTopic")
public class CacheUpdateConsumer {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void onMessage(MessageExt messageExt) {
        String cacheKey = new String(messageExt.getBody());
        redisTemplate.delete(cacheKey);
    }
}

這種方法的好處是將緩存的更新操作與主業(yè)務(wù)流程解耦,避免阻塞主線(xiàn)程,同時(shí)還能處理數(shù)據(jù)庫(kù)更新后由于網(wǎng)絡(luò)問(wèn)題或并發(fā)問(wèn)題導(dǎo)致的緩存更新滯后情況。當(dāng)然,實(shí)現(xiàn)這一策略相對(duì)復(fù)雜,需要對(duì)數(shù)據(jù)庫(kù)的binlog機(jī)制有深入理解和定制開(kāi)發(fā)。

總結(jié)

在分布式系統(tǒng)中,為了保證緩存與數(shù)據(jù)庫(kù)雙寫(xiě)一致性,可以采用以下方案:

1. 讀取操作:

? 先嘗試從緩存讀取數(shù)據(jù),若緩存命中,則直接返回緩存中的數(shù)據(jù)。

? 若緩存未命中,則從數(shù)據(jù)庫(kù)讀取數(shù)據(jù),并將數(shù)據(jù)放入緩存。

2. 更新操作:

? 在更新數(shù)據(jù)時(shí),首先在數(shù)據(jù)庫(kù)進(jìn)行寫(xiě)入操作,確保主數(shù)據(jù)庫(kù)數(shù)據(jù)的即時(shí)更新。

? 為了減少數(shù)據(jù)不一致窗口,采用異步方式處理緩存更新,具體做法是監(jiān)聽(tīng)數(shù)據(jù)庫(kù)的binlog事件,異步進(jìn)行刪除緩存。

? 在一主多從的場(chǎng)景下,為了確保數(shù)據(jù)一致性,需要等待所有從庫(kù)的binlog事件都被處理后才刪除緩存(確保全部從庫(kù)均已更新)。

同時(shí),還需注意以下要點(diǎn):

? 對(duì)于高并發(fā)環(huán)境,可能需要結(jié)合分布式鎖、消息隊(duì)列或緩存失效延時(shí)等技術(shù),進(jìn)一步確保并發(fā)寫(xiě)操作下的數(shù)據(jù)一致性。

? 異步處理binlog時(shí),務(wù)必考慮異常處理機(jī)制和重試策略,確保binlog事件能夠正確處理并執(zhí)行緩存更新操作。

責(zé)任編輯:武曉燕 來(lái)源: 碼農(nóng)Academy
相關(guān)推薦

2021-06-04 09:56:12

RedisMySQL美團(tuán)

2024-08-06 09:42:23

2024-05-08 16:37:17

MySQLRedis數(shù)據(jù)庫(kù)

2020-09-03 09:45:38

緩存數(shù)據(jù)庫(kù)分布式

2022-03-31 08:21:14

數(shù)據(jù)庫(kù)緩存雙寫(xiě)數(shù)據(jù)一致性

2023-05-26 07:34:50

RedisMySQL緩存

2021-12-14 07:15:57

MySQLRedis數(shù)據(jù)

2022-10-19 12:22:53

并發(fā)扣款一致性

2021-06-11 09:21:58

緩存數(shù)據(jù)庫(kù)Redis

2022-12-05 08:24:32

mongodb數(shù)據(jù)庫(kù)數(shù)據(jù)

2024-01-22 08:52:00

AQS雙異步數(shù)據(jù)一致性

2024-01-15 10:38:20

多級(jí)緩存數(shù)據(jù)一致性分布式緩存

2024-08-20 16:13:52

2025-04-27 08:52:21

Redis數(shù)據(jù)庫(kù)緩存

2017-07-02 16:28:06

MySQL數(shù)據(jù)庫(kù)集群

2024-12-26 15:01:29

2019-08-30 12:46:10

并發(fā)扣款查詢(xún)SQL

2024-01-10 08:01:55

高并發(fā)場(chǎng)景悲觀鎖

2025-03-27 08:20:54

2020-08-05 08:46:10

NFS網(wǎng)絡(luò)文件系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)