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