前言
由于緩存的高并發(fā)和高性能已經(jīng)在各種項(xiàng)目中被廣泛使用,在讀取緩存這方面基本都是一致的,大概都是按照下圖的流程進(jìn)行操作:
但是在更新緩存方面,是更新完數(shù)據(jù)庫再更新緩存還是直接刪除緩存呢?又或者是先刪除緩存再更新數(shù)據(jù)庫?在這一點(diǎn)上就值得探討了。
一致性方案
在實(shí)際項(xiàng)目開發(fā)中需要保證數(shù)據(jù)庫和緩存中的數(shù)據(jù)一致,否則人家充值了100塊,不斷刷新卻還是顯示0.01元,豈不是尷尬?從理論上來說,為緩存設(shè)置過期時(shí)間是最終保證數(shù)據(jù)一致性的解決方案,采用這種方案的話,所有的寫操作都是以數(shù)據(jù)庫為準(zhǔn),如果數(shù)據(jù)庫寫入成功但是緩存更新失敗,只要緩存到過期時(shí)間之后后面讀緩存時(shí)自然會(huì)在數(shù)據(jù)庫中讀取新的值然后更新緩存。接下來探討的思路主要的方向是在不依賴為緩存設(shè)置過期時(shí)間的前提下如何保證數(shù)據(jù)一致性。這里主要探討三種方案:
①先更新數(shù)據(jù)庫,再更新緩存
②先刪除緩存,再更新數(shù)據(jù)庫
③先更新數(shù)據(jù)庫,再刪除緩存
先更新數(shù)據(jù)庫再更新緩存
這種方案是普遍被反對的(在我的認(rèn)知范圍中~),為啥呢?為啥這種方案就被反對呢?原因主要有兩方面,請聽我細(xì)細(xì)道來:
首先從數(shù)據(jù)安全方面考慮,如果同時(shí)有請求A和請求B同時(shí)進(jìn)行操作,A先更新了數(shù)據(jù)庫的一條數(shù)據(jù),隨后B馬上有更新了該條數(shù)據(jù),但是可能因?yàn)榫W(wǎng)絡(luò)延遲等原因,B卻比A先更新了緩存,就會(huì)出現(xiàn)一種什么情況呢?緩存中的數(shù)據(jù)并不最新的B更新過的數(shù)據(jù),就導(dǎo)致了數(shù)據(jù)不一致的情況。
其次從業(yè)務(wù)場景方面考慮,如果是一個(gè)寫數(shù)據(jù)庫較多而讀數(shù)據(jù)庫較少的業(yè)務(wù),如果采用這種方案就會(huì)導(dǎo)致數(shù)據(jù)還沒讀緩存就會(huì)被頻繁更新,白白浪費(fèi)性能。
綜合以上兩方面的考慮,這種方案果斷pass。下面的兩種方案就是爭議較大的兩種方案了,到底是先刪緩存再更新數(shù)據(jù)庫還是先更新數(shù)據(jù)庫再刪除緩存?
先刪緩存再更新數(shù)據(jù)庫
如果同時(shí)有一個(gè)請求A進(jìn)行更新操作,請求B進(jìn)行查詢操作,就可能會(huì)出現(xiàn)A請求進(jìn)行寫操作前會(huì)刪除緩存,B請求剛好此時(shí)進(jìn)來發(fā)現(xiàn)緩存是空的,B請求就會(huì)查詢數(shù)據(jù)庫,如果此時(shí)A請求的寫操作還未完成,B請求查詢到的就還是舊的值,還是會(huì)將舊的值寫入緩存,A請求將新的值寫入數(shù)據(jù)庫,此時(shí)就會(huì)導(dǎo)致數(shù)據(jù)不一致的問題,如果不采用給緩存設(shè)置過期時(shí)間的策略,該數(shù)據(jù)永遠(yuǎn)都是臟數(shù)據(jù)。
解決這種情況可以采用延時(shí)雙刪的策略,就是在更新數(shù)據(jù)庫之前先刪除緩存,然后對數(shù)據(jù)庫進(jìn)行寫入操作,數(shù)據(jù)庫更新完成之后再次進(jìn)行刪除緩存的操作,目的是刪除讀請求可能造成的緩存臟數(shù)據(jù),第二次刪除緩存之前可以休眠幾秒,具體時(shí)間開發(fā)者可以評估一下自己項(xiàng)目讀數(shù)據(jù)業(yè)務(wù)邏輯的耗時(shí),然后在該耗時(shí)基礎(chǔ)上加幾百ms即可,這么做的目的就是確保讀請求結(jié)束寫請求可以刪除讀請求造成的臟數(shù)據(jù)。如果MySQL采用的是讀寫分離的架構(gòu),可能由于主從延時(shí)的原因造成數(shù)據(jù)不一致,可以在寫操作完成之后根據(jù)主從延時(shí)時(shí)間休眠一下然后再進(jìn)行刪除緩存的操作。延時(shí)雙刪的偽代碼如下:
# 偽代碼
def delay_delete():
redis.delete('name') # 更新數(shù)據(jù)庫之前先刪除緩存
sql = 'update info set name='lili' where id=1;' # 更新數(shù)據(jù)庫
cursor.execute(sql)
time.sleep(1) # 如果mysql是主從架構(gòu)則休眠主從延時(shí)的時(shí)間再多幾百ms
redis.delete('name') # 再次刪除緩存
那會(huì)不會(huì)存在第二次刪除緩存失敗的情況呢?如果第二次刪除失敗,還是會(huì)造成緩存和數(shù)據(jù)庫不一致的問題,又如何解決呢?且看下一種方案。
先更新數(shù)據(jù)庫再刪除緩存
老外提出了一個(gè)緩存更新方案Cache−AsidepatternCache-Aside patternCache−Asidepattern,文章中提到**應(yīng)用程序應(yīng)該從cache中獲取數(shù)據(jù),如果獲取成功直接返回,如果沒有獲取成功,則從數(shù)據(jù)庫中獲取,成功后放到緩存中,更新數(shù)據(jù)時(shí)應(yīng)該先把數(shù)據(jù)存到數(shù)據(jù)庫中成功后再讓緩存失效。**原文如下
If an application updates information, it can follow the write-through strategy by making the modification to the data store, and by invalidating the corresponding item in the cache.
When the item is next required, using the cache-aside strategy will cause the updated data to be retrieved from the data store and added back into the cache.
這種方案會(huì)不會(huì)產(chǎn)生數(shù)據(jù)不一致的情況呢?比如下述這種情況:
有兩個(gè)請求A和B,A進(jìn)行查詢同時(shí)B進(jìn)行更新,假設(shè)發(fā)生下述情況:
①此時(shí)緩存剛好失效
②請求A 就會(huì)去查詢數(shù)據(jù)庫得到一個(gè)舊的值
③請求B將新的值寫入數(shù)據(jù)庫
④請求B寫入成功后刪除緩存
⑤請求A將查到的機(jī)制寫入緩存,產(chǎn)生臟數(shù)據(jù)...
如果發(fā)聲上述情況,確實(shí)會(huì)產(chǎn)生數(shù)據(jù)不一致的情況,但是XDM想一想,發(fā)生這種情況的概率是多少呢?如果先要產(chǎn)生這種結(jié)果,就必須有一個(gè)條件,就是請求B的操作時(shí)間非常短,短到什么程度呢,就是請求B寫入數(shù)據(jù)庫的操作要比請求A從數(shù)據(jù)庫中讀取數(shù)據(jù)的速度要快(因?yàn)閞edis非??欤虼瞬僮鱮edis的時(shí)間可以暫且忽略),只有這種情況下④才可能比⑤先發(fā)聲,但是數(shù)據(jù)庫的讀操作要遠(yuǎn)比寫操作快的多,不然做讀寫分離干嘛呢?所以這種情況發(fā)生的概率是非常非常非常的低,但是如果強(qiáng)迫癥患者出現(xiàn)必須要解決怎么辦呢?就可以采用給緩存設(shè)置過期時(shí)間或者采用第二種方案的延時(shí)雙刪策略,保證讀請求完成之后在進(jìn)行刪除操作。
最后的問題
還有問題呀,就是最終解決方案三可能 出現(xiàn)的極低概率的數(shù)據(jù)不一致的方案是采用方案二的延時(shí)雙刪策略,可是在方案二中也說了,如果出現(xiàn)緩存刪除失敗的情況咋辦?那不是還會(huì)出現(xiàn)數(shù)據(jù)不一致的問題嗎?這個(gè)問題到底如何解決呢?這里提供一個(gè)重試機(jī)制,刪除失敗就重試一次唄,這里提供一種重試的方案。
①更新數(shù)據(jù)庫
②由于各種原因緩存刪除失敗
③將刪除失敗的緩存放入消息隊(duì)列中
④業(yè)務(wù)代碼從消息隊(duì)列中獲取需要?jiǎng)h除的key
⑤繼續(xù)嘗試刪除操作,直到成功
總結(jié)
到此這篇關(guān)于MySQL與Redis如何保證數(shù)據(jù)一致性的文章就介紹到這了,更多相關(guān)MySQL與Redis數(shù)據(jù)一致性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- Redis緩存常用4種策略原理詳解
- 聊一聊Redis與MySQL雙寫一致性如何保證
- 詳解redis緩存與數(shù)據(jù)庫一致性問題解決
- redis實(shí)現(xiàn)分布式的方法總結(jié)
- 面試常問:如何保證Redis緩存和數(shù)據(jù)庫的數(shù)據(jù)一致性