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