講講Redis緩存更新一致性
當(dāng)執(zhí)行寫操作后,需要保證從緩存讀取到的數(shù)據(jù)與數(shù)據(jù)庫中持久化的數(shù)據(jù)是一致的,因此需要對緩存進行更新。
因為涉及到數(shù)據(jù)庫和緩存兩步操作,難以保證更新的原子性。
在設(shè)計更新策略時,我們需要考慮多個方面的問題:
- 對系統(tǒng)吞吐量的影響:比如更新緩存策略產(chǎn)生的數(shù)據(jù)庫負載小于刪除緩存策略的負載
- 并發(fā)安全性:并發(fā)讀寫時某些異常操作順序可能造成數(shù)據(jù)不一致,如緩存中長期保存過時數(shù)據(jù)
- 更新失敗的影響:若某個操作失敗,如何對業(yè)務(wù)影響降到最小
- 檢測和修復(fù)故障的難度: 操作失敗導(dǎo)致的錯誤會在日志留下詳細的記錄容易檢測和修復(fù)。并發(fā)問題導(dǎo)致的數(shù)據(jù)錯誤沒有明顯的痕跡難以發(fā)現(xiàn),且在流量高峰期更容易產(chǎn)生并發(fā)錯誤產(chǎn)生的業(yè)務(wù)風(fēng)險較大。
更新緩存有兩種方式:
- 刪除失效緩存: 讀取時會因為未命中緩存而從數(shù)據(jù)庫中讀取新的數(shù)據(jù)并更新到緩存中
- 更新緩存: 直接將新的數(shù)據(jù)寫入緩存覆蓋過期數(shù)據(jù)
更新緩存和更新數(shù)據(jù)庫有兩種順序:
- 先數(shù)據(jù)庫后緩存
- 先緩存后數(shù)據(jù)庫
兩兩組合共有四種更新策略,現(xiàn)在我們逐一進行分析。
并發(fā)問題通常由于后開始的線程卻先完成操作導(dǎo)致,我們把這種現(xiàn)象稱為“搶跑”。下面我們逐一分析四種策略中“搶跑”帶來的錯誤。
先更新數(shù)據(jù)庫,再刪除緩存
若數(shù)據(jù)庫更新成功,刪除緩存操作失敗,則此后讀到的都是緩存中過期的數(shù)據(jù),造成不一致問題。
可能發(fā)生的并發(fā)錯誤:
先更新數(shù)據(jù)庫,再更新緩存
同刪除緩存策略一樣,若數(shù)據(jù)庫更新成功緩存更新失敗則會造成數(shù)據(jù)不一致問題。
可能發(fā)生的并發(fā)錯誤:
當(dāng)兩個寫線程發(fā)生沖突時,可以通過比較數(shù)據(jù)版本方式避免線程A寫入舊的數(shù)據(jù)。
先刪除緩存,再更新數(shù)據(jù)庫
可能發(fā)生的并發(fā)錯誤:
先更新緩存,再更新數(shù)據(jù)庫
若緩存更新成功數(shù)據(jù)庫更新失敗, 則此后讀到的都是未持久化的數(shù)據(jù)。因為緩存中的數(shù)據(jù)是易失的,這種狀態(tài)非常危險。
因為數(shù)據(jù)庫因為鍵約束導(dǎo)致寫入失敗的可能性較高,所以這種策略風(fēng)險較大。
可能發(fā)生的并發(fā)錯誤:
異步更新
雙寫更新的邏輯復(fù)雜,一致性問題較多?,F(xiàn)在我們可以采用訂閱數(shù)據(jù)庫更新的方式來更新緩存。
阿里巴巴開源了mysql數(shù)據(jù)庫binlog的增量訂閱和消費組件 - canal。
我們可以采用API服務(wù)器只寫入數(shù)據(jù)庫,而另一個線程訂閱數(shù)據(jù)庫 binlog 增量進行緩存更新的策略。關(guān)注Java知音公眾號,回復(fù)“面試題聚合”,送你一份面試題寶典
這種策略存在和先更新數(shù)據(jù)庫后刪除緩存類似的并發(fā)問題:
這個問題同樣可以采用異步線程更新緩存,且寫入緩存時比較數(shù)據(jù)版本的方法來解決。