首頁 > 科技

MySQL和Redis如何保證資料一致性?

2021-07-25 14:10:56

前言

由於快取的高併發和高效能已經在各種項目中被廣泛使用,在讀取快取這方面基本都是一致的,大概都是按照下圖的流程進行操作:

但是在更新快取方面,是更新完資料庫再更新快取還是直接刪除快取呢?又或者是先刪除快取再更新資料庫?在這一點上就值得探討了。

一致性方案

在實際項目開發中需要保證資料庫和快取中的資料一致,否則人家充值了100塊,不斷重新整理卻還是顯示0.01元,豈不是尷尬?從理論上來說,為快取設定過期時間是最終保證資料一致性的解決方案,採用這種方案的話,所有的寫操作都是以資料庫為準,如果資料庫寫入成功但是快取更新失敗,只要快取到過期時間之後後面讀快取時自然會在資料庫中讀取新的值然後更新快取。接下來探討的思路主要的方向是在不依賴為快取設定過期時間的前提下如何保證資料一致性。這裡主要探討三種方案:

①先更新資料庫,再更新快取

②先刪除快取,再更新資料庫

③先更新資料庫,再刪除快取

先更新資料庫再更新快取

這種方案是普遍被反對的(在我的認知範圍中~),為啥呢?為啥這種方案就被反對呢?原因主要有兩方面,請聽我細細道來:

首先從資料安全方面考慮,如果同時有請求A和請求B同時進行操作,A先更新了資料庫的一條資料,隨後B馬上有更新了該條資料,但是可能因為網路延遲等原因,B卻比A先更新了快取,就會出現一種什麼情況呢?快取中的資料並不最新的B更新過的資料,就導致了資料不一致的情況。

其次從業務場景方面考慮,如果是一個寫資料庫較多而讀資料庫較少的業務,如果採用這種方案就會導致資料還沒讀快取就會被頻繁更新,白白浪費效能。

綜合以上兩方面的考慮,這種方案果斷pass。下面的兩種方案就是爭議較大的兩種方案了,到底是先刪快取再更新資料庫還是先更新資料庫再刪除快取?

先刪快取再更新資料庫

如果同時有一個請求A進行更新操作,請求B進行查詢操作,就可能會出現A請求進行寫操作前會刪除快取,B請求剛好此時進來發現快取是空的,B請求就會查詢資料庫,如果此時A請求的寫操作還未完成,B請求查詢到的就還是舊的值,還是會將舊的值寫入快取,A請求將新的值寫入資料庫,此時就會導致資料不一致的問題,如果不採用給快取設定過期時間的策略,該資料永遠都是髒資料。

解決這種情況可以採用延時雙刪的策略,就是在更新資料庫之前先刪除快取,然後對資料庫進行寫入操作,資料庫更新完成之後再次進行刪除快取的操作,目的是刪除讀請求可能造成的快取髒資料,第二次刪除快取之前可以休眠幾秒,具體時間開發者可以評估一下自己項目讀資料業務邏輯的耗時,然後在該耗時基礎上加幾百ms即可,這麼做的目的就是確保讀請求結束寫請求可以刪除讀請求造成的髒資料。如果MySQL採用的是讀寫分離的架構,可能由於主從延時的原因造成資料不一致,可以在寫操作完成之後根據主從延時時間休眠一下然後再進行刪除快取的操作。延時雙刪的虛擬碼如下:

那會不會存在第二次刪除快取失敗的情況呢?如果第二次刪除失敗,還是會造成快取和資料庫不一致的問題,又如何解決呢?且看下一種方案。

先更新資料庫再刪除快取

老外提出了一個快取更新方案CacheAsidepatternCache-Aside patternCacheAsidepattern,文章中提到**應用程式應該從cache中獲取資料,如果獲取成功直接返回,如果沒有獲取成功,則從資料庫中獲取,成功後放到快取中,更新資料時應該先把資料存到資料庫中成功後再讓快取失效。**原文如下

If an application updates information, it can follow the write-through strategy by making the modification to the data store, and by invalidating the corresponding item in the cache.

When the item is next required, using the cache-aside strategy will cause the updated data to be retrieved from the data store and added back into the cache.

這種方案會不會產生資料不一致的情況呢?比如下述這種情況:

有兩個請求A和B,A進行查詢同時B進行更新,假設發生下述情況:

①此時快取剛好失效

②請求A 就會去查詢資料庫得到一箇舊的值

③請求B將新的值寫入資料庫

④請求B寫入成功後刪除快取

⑤請求A將查到的機制寫入快取,產生髒資料...

如果發聲上述情況,確實會產生資料不一致的情況,但是XDM想一想,發生這種情況的概率是多少呢?如果先要產生這種結果,就必須有一個條件,就是請求B的操作時間非常短,短到什麼程度呢,就是請求B寫入資料庫的操作要比請求A從資料庫中讀取資料的速度要快(因為redis非常快,因此操作redis的時間可以暫且忽略),只有這種情況下④才可能比⑤先發聲,但是資料庫的讀操作要遠比寫操作快的多,不然做讀寫分離幹嘛呢?所以這種情況發生的概率是非常非常非常的低,但是如果強迫症患者出現必須要解決怎麼辦呢?就可以採用給快取設定過期時間或者採用第二種方案的延時雙刪策略,保證讀請求完成之後在進行刪除操作。

最後的問題

還有問題呀,就是最終解決方案三可能 出現的極低概率的資料不一致的方案是採用方案二的延時雙刪策略,可是在方案二中也說了,如果出現快取刪除失敗的情況咋辦?那不是還會出現資料不一致的問題嗎?這個問題到底如何解決呢?這裡提供一個重試機制,刪除失敗就重試一次唄,這裡提供一種重試的方案。

①更新資料庫

②由於各種原因快取刪除失敗

③將刪除失敗的快取放入訊息佇列中

④業務程式碼從訊息佇列中獲取需要刪除的key

⑤繼續嘗試刪除操作,直到成功

想了解更多精彩內容,快來關注計算機java程式設計


IT145.com E-mail:sddin#qq.com