業(yè)務(wù)使用Redis做緩存,當(dāng)有數(shù)據(jù)更新時(shí),如何保證緩存及時(shí)更新
讀數(shù)據(jù)流程
請(qǐng)求到來(lái),業(yè)務(wù)代碼會(huì)先查Redis,查不到再去查DB,并將結(jié)果寫(xiě)入Redis
寫(xiě)數(shù)據(jù)方案
1. 先刪除緩存,再更新DB
可行性
先刪除緩存,再更新DB,下次讀請(qǐng)求到來(lái)會(huì)從數(shù)據(jù)庫(kù)查到新的數(shù)據(jù)更新到緩存中。如果先更新緩存,在更新DB,更新DB失敗會(huì)導(dǎo)致數(shù)據(jù)不一致。
問(wèn)題
容災(zāi)不足
如果刪除緩存失敗的情況,如果業(yè)務(wù)繼續(xù)進(jìn)行,更新DB,那么在緩存過(guò)期之前仍然查到的是舊數(shù)據(jù)。如果業(yè)務(wù)返回失敗,則對(duì)Redis變成了強(qiáng)依賴(lài)。
并發(fā)不安全
考慮如下場(chǎng)景:
- A請(qǐng)求刪除緩存,A請(qǐng)求更新DB
- B請(qǐng)求查詢(xún)緩存,不存在
- B請(qǐng)求查詢(xún)DB,查到舊數(shù)據(jù)(更新未完成),寫(xiě)入緩存
- A請(qǐng)求更新DB完成
這就導(dǎo)致緩存中仍存的舊數(shù)據(jù),數(shù)據(jù)不一致。
2. 先更新DB,再刪除緩存
這種策略解決了方法1中的并發(fā)問(wèn)題,但是還是有極小可能存在并發(fā)問(wèn)題,考慮如下情況:
- 請(qǐng)求A查詢(xún)緩存,緩存剛好失效
- 請(qǐng)求A查詢(xún)DB,得到一個(gè)舊值
- 請(qǐng)求B更新數(shù)據(jù)庫(kù)
- 請(qǐng)求B刪除緩存
- 請(qǐng)求A將查到的舊值寫(xiě)入緩存
這種情況確實(shí)會(huì)產(chǎn)生數(shù)據(jù)不一致,但是考慮到DB的讀操作總是比寫(xiě)操作快的多,這種場(chǎng)景基本不可能出現(xiàn)。
如何杜絕并發(fā)問(wèn)題
延遲異步刪,保證讀操作完成后再刪除緩存。
如何容災(zāi)
上述方案中如果刪除緩存失敗了怎么辦?
引入消息隊(duì)列
- 更新DB
- 刪除緩存,如果失敗將要?jiǎng)h除的key發(fā)送至消息隊(duì)列
- 消費(fèi)消息,獲得需要?jiǎng)h除的key,刪除key緩存直到成功
訂閱binlog
上述方法對(duì)業(yè)務(wù)代碼的侵入性比較大,為此可以啟動(dòng)一個(gè)程序訂閱MySQL的binlog用來(lái)發(fā)現(xiàn)數(shù)據(jù)更新,流程如下:
- 業(yè)務(wù)代碼更新數(shù)據(jù)庫(kù),MySQL將更新操作寫(xiě)入binlog
- 訂閱程序提取中更新的數(shù)據(jù)以及key,嘗試刪除key的緩存
- 如果刪除緩存失敗,將key發(fā)送至消息隊(duì)列
- 消費(fèi)者程序從消息隊(duì)列中獲取待刪除的key,重試刪除直到成功。