Redis快取與資料庫資料一致性

2022-03-09 18:47:51 字數 4467 閱讀 2837

**:

寫流程:先刪除快取,刪除之後再更新db,再非同步將資料刷回快取。如果先更新資料庫再更新快取,更新資料庫時,程式訪問快取時還是舊的資料。

讀流程:先讀快取,如果快取沒讀到,則去讀db,之後再非同步將資料刷回快取。

缺點

容災不足

第一步del快取失敗』,如果繼續執行,那麼從』更新完db』到非同步』重新整理快取』快取期間,資料處於滯後狀態。而且如果快取處於不可寫狀態,那麼非同步重新整理那步也可能會失敗,那快取就會長期處於舊資料。

併發問題

寫寫併發:如果同時有多個伺服器的多個執行緒進行』步驟1.2更新db』,更新db完成之後,它們就要進行非同步刷快取,多伺服器的非同步操作是無法保證順序的,所以後面的重新整理操作存在相互覆蓋的併發問題,也就是說,存在先更新的db操作,反而很晚才去重新整理快取,那這個時候,資料也是錯的

讀寫併發:伺服器a在進行』讀操作』,在a伺服器剛完成2.2時,伺服器b在進行』寫操作』,假設b伺服器1.3完成之後,伺服器a的1.3才被執行,這個時候就相當於更新前的老資料寫入快取,最終資料還是錯的。

方案一 乙個比較大的缺陷在於重新整理快取有可能會失敗,而失敗之後快取中資料就一直會處於錯誤狀態,所以它並不能保證資料的最終一致性」為了保證「資料最終一致性」,我們引入binlog,通過解析binlog來重新整理快取,這樣即使重新整理失敗,依然可以進行日誌回放,再次重新整理快取。

第一步先刪除快取,刪除之後再更新db,我們監聽從庫(資源少的話主庫也ok)的binlog,通過分析binlog我們解析出需要需要重新整理的資料,然後讀主庫把最新的資料寫入快取。

這裡需要提一下:最後重新整理前的讀主庫或者讀從庫,甚至不讀庫直接通過binlog解析出需要的資料都是ok的,這由業務決定,比如重新整理的資料只是表的一行,那直接通過binlog就完全能解析出來;然而如果需要重新整理的資料來自多行,多張表,甚至多個庫的話,那就需要讀主庫或是從庫才行

第一步先讀快取,如果快取沒讀到,則去讀db,之後再非同步將資料刷回快取

優點:

容災寫步驟1.4或1.5 如果失敗,可以進行日誌回放,再次重試。無論步驟1.1是否刪除成功,後續的重新整理操作是***的。

缺點:

只適合簡單業務(每次需要重新整理的資料,都來自單錶單行),複雜業務容易發生併發問題。

為什麼複雜業務就不行呢?我舉個例子

我們假設 乙個訂單 = a表資訊 + b表資訊

由於a表先變化,經過1,2,3步後,執行緒1獲取了a』b (a表是新資料,b表的老資料),當執行緒1還沒來得及重新整理快取時,併發發生了:

此時,b表發生了更新,經過4,5,6,7將最新的資料a』b』寫入快取,此時此刻快取資料是符合要求的。

但是,後來執行緒1進行了第8步,將a』b寫入資料,使得快取最終結果 與 db 不一致。

缺點1的改進

當ab表的更新發生在乙個事務內時,不管執行緒1、執行緒2如何讀取,他們都能獲取兩張表的最新資料,所以重新整理快取的資料都是符合要求的。

但是這種方案具有侷限性:那就是只對單次更新有效,或者說更新頻率低的情況下才適應,比如我們併發的單獨更新c表,併發問題依然會發生。

所以這種方案只針對多表單次更新的情況。

每張表的更新,在同步快取時,只獲取該錶的字段覆蓋快取。

這樣,執行緒1,執行緒2總能獲取對應表最新的字段,而且databus對於同表同行會以序列的形式通知下游,所以能保證快取的最終一致性。

這裡有一點需要提一下:更新「某張表多行記錄「時,這個操作要在乙個事務內,不然併發問題依然存在,正如前面分析的

依然是併發問題

即使對於缺點1我們提出了改進方案,雖然它解決了部分問題,但在極端場景下依然存在併發問題。

這個場景,就是快取中沒有資料的情況:

這個時候,我們在上面提到的「增量更新」就不起作用了,我們需要讀取所有的表來拼湊出初始資料,那這個時候又涉及到讀所有表的操作了,那我們在缺點1中提到的併發問題會再次發生

適合使用的場景:業務簡單,讀寫qps比較低的情況。

這個方案優缺點都比較明顯,binlog用來重新整理快取是乙個很棒的選擇,它天然的順序性用來做同步操作很具有優勢;其實它的併發問題來自於canal 或 databus。拿databus來說,由於不同行、表、庫的binlog的消費並不是時間序列的,那怎麼解決這個問題呢。

問題就來自於「讀資料庫」 + 「寫快取」 之間的交錯併發,那怎麼來避免呢?

有乙個方法就是:序列化,我們利用mq將所有「讀資料庫」 + 「寫快取」的步驟序列化

第一步先刪除快取,刪除之後再更新db,我們監聽從庫(資源少的話主庫也ok)的binlog,通過分析binlog我們解析出需要需要重新整理的資料標識,然後將資料標識寫入mq,接下來就消費mq,解析mq訊息來讀庫獲取相應的資料重新整理快取。

關於mq序列化,大家可以去了解一下kafka partition 機制 ,這裡就不詳述了

第一步先讀快取,如果快取沒讀到,則去讀db,之後再非同步將資料標識寫入mq(這裡mq與寫流程的mq是同乙個),接下來就消費mq,解析mq訊息來讀庫獲取相應的資料重新整理快取。

優點

容災完善

寫流程容災分析

讀流程容災分析

無併發問題

這個方案讓「讀庫 + 刷快取」的操作序列化,這就不存在老資料覆蓋新資料的併發問題了。

在前乙個方案的基礎上實現「強一致性」

強一致性,包含兩種含義:

首先我們來分析一下,既然已經實現了「最終一致性」,那它和「強一致性」的區別是什麼呢?沒錯,就是「時間差」,所以:

「最終一致性方案」 + 「時間差」 = 「強一致性方案」

那我們的工作呢,就是加上時間差,實現方式:我們加乙個快取,將近期被修改的資料進行標記鎖定。讀的時候,標記鎖定的資料強行走db,沒鎖定的資料,先走快取

寫流程:

何為標記鎖定呢?比如你可以設定乙個有效期為10s的key,key存在即為鎖定。一般來說10s對於後面的同步操作來說基本是夠了~

如果說,還想更嚴謹一點,怕db主從延遲太久、mq延遲太久,或databus監聽的從庫掛機之類的情況,我們可以考慮增加乙個監控定時任務。

比如我們增加乙個時間間隔2s的worker的去對比以下兩個資料:

資料1: 可由步驟1.1獲得,並儲存

資料2: 需要由binlog中解析獲得,需要透傳到mq,這樣後面就能儲存了

這裡提一下:如果多庫的情況的話,儲存這兩個key需要與庫一一對應

如果 時間1 vs 時間2 相差超過5s,那我們就自動把相應的快取分片讀降級。

讀流程:

方案分析

優點剖析

1. 容災完善

我們一步一步來分析:

寫流程容災分析

讀流程容災分析

2. 無併發問題

這個方案讓「讀庫 + 刷快取」的操作序列化,這就不存在老資料覆蓋新資料的併發問題了

缺點剖析

1. 增加cache_0強依賴

這個其實有點沒辦法,你要強一致性,必然要犧牲一些的。

但是呢,你這個可以吧cache_0設計成多機器多分片,這樣的話,即使部分分片掛了,也只有小部分流量透過cache直接打到db上,這是完全是可接受的

2. 複雜度是比較高的

涉及到databus、mq、定時任務等等元件,實現起來複雜度還是有的

redis快取與資料庫一致性

實時同步 對強制性要求比較高的,應採用實時同步方案,先查詢快取若查詢不到再去db中查詢,然後儲存到快取 更新快取時,先更新資料庫,再將快取的設定過期 建議不要去更新快取內容,直接設定快取過期 1.cacheable 查詢時使用,注意long型別需要轉化為string型別,否則會拋棄異常 2.cach...

redis快取與資料庫一致性問題

不一致產生的原因 我們在使用redis過程中,通常會這樣做 先讀取快取,如果快取不存在,則讀取資料庫。偽 如下 object stuobj new object public stu getstufromcache string key return stu 寫資料庫的偽 如下 public voi...

快取與資料庫一致性

此時系統的讀寫流量很小,這個時候所有的讀寫操作都在主庫 此時,從庫的角色只是作為災備。風險分析 從資料一致性的角度來看沒有任何問題,所有讀寫操作都在主庫 隨著業務的前進和流量的激增,會出現大表和資料庫寫入效能下降的問題。我們可以通過分庫的方式,提公升資料庫單機的qps壓下來 通過分表的方式,降低單錶...