上回程序員阿旺為了提升數(shù)據(jù)訪問的性能,引入 Redis 作為 MySQL 緩存層,但是這件事情并不是那么簡單,因?yàn)檫€要考慮 Redis 和 MySQL 雙寫一致性的問題。阿旺經(jīng)過一番周折,最終選用了「先更新數(shù)據(jù)庫,再刪緩存」的策略,原因是這個(gè)策略即使在并發(fā)讀寫時(shí),也能最大程度保證數(shù)據(jù)一致性。聰明的阿旺還搞了個(gè)兜底的方案,就是給緩存加上了過期時(shí)間。本以為就這樣不會在出現(xiàn)數(shù)據(jù)一致性的問題,結(jié)果將功能上線后,老板還是收到用戶的投訴「說自己明明更新了數(shù)據(jù),但是數(shù)據(jù)要過一段時(shí)間才生效」,客戶接受不了。老板轉(zhuǎn)告給了阿旺,阿旺得知又有 Bug 就更慌了,立馬就登錄服務(wù)器去排查問題,查看日志后得知了原因。「先更新數(shù)據(jù)庫, 再刪除緩存」其實(shí)是兩個(gè)操作,這次客戶投訴的問題就在于,在刪除緩存(第二個(gè)操作)的時(shí)候失敗了,導(dǎo)致緩存中的數(shù)據(jù)是舊值,而數(shù)據(jù)庫是最新值。好在之前給緩存加上了過期時(shí)間,所以才會出現(xiàn)客戶說的過一段時(shí)間才更新生效的現(xiàn)象,假設(shè)如果沒有這個(gè)過期時(shí)間的兜底,那后續(xù)的請求讀到的就會一直是緩存中的舊數(shù)據(jù),這樣問題就更大了。所以新的問題來了,如何保證「先更新數(shù)據(jù)庫 ,再刪除緩存」這兩個(gè)操作能執(zhí)行成功?阿旺分析出問題后,慌慌張張的向老板匯報(bào)了問題。老板知道事情后,又給了阿旺幾天來解決這個(gè)問題,畫餅的事情這次沒有再提了。
阿旺會用什么方式來解決這個(gè)問題呢?
老板畫的餅事情,能否兌現(xiàn)給阿旺呢?
如何保證兩個(gè)操作都能執(zhí)行成功?
這次用戶的投訴是因?yàn)樵趧h除緩存(第二個(gè)操作)的時(shí)候失敗了,導(dǎo)致緩存還是舊值,而數(shù)據(jù)庫是最新值,造成數(shù)據(jù)庫和緩存數(shù)據(jù)不一致的問題,會對敏感業(yè)務(wù)造成影響。舉個(gè)例子,來說明下。應(yīng)用要把數(shù)據(jù) X 的值從 1 更新為 2,先成功更新了數(shù)據(jù)庫,然后在 Redis 緩存中刪除 X 的緩存,但是這個(gè)操作卻失敗了,這個(gè)時(shí)候數(shù)據(jù)庫中 X 的新值為 2,Redis 中的 X 的緩存值為 1,出現(xiàn)了數(shù)據(jù)庫和緩存數(shù)據(jù)不一致的問題。那么,后續(xù)有訪問數(shù)據(jù) X 的請求,會先在 Redis 中查詢,因?yàn)榫彺娌]有 誒刪除,所以會緩存命中,但是讀到的卻是舊值 1。其實(shí)不管是先操作數(shù)據(jù)庫,還是先操作緩存,只要第二個(gè)操作失敗都會出現(xiàn)數(shù)據(jù)一致的問題。問題原因知道了,該怎么解決呢?有兩種方法: