做分銷網(wǎng)站系統(tǒng)下載網(wǎng)站建立具體步驟是
提示:文章寫完后,目錄可以自動(dòng)生成,如何生成可參考右邊的幫助文檔
文章目錄
- 緩存一致性問題
- 1、先更新緩存,再更新DB
- 方案二:先更新DB,再更新緩存
- 方案三:先刪緩存,再寫數(shù)據(jù)庫
- 推薦1:==延遲雙刪==
- 方案四:先寫數(shù)據(jù)庫,再刪緩存
- 刪除緩存失敗怎么辦?
- 方案一:設(shè)置過期時(shí)間
- 方案二:同步重試
- 方案三:消息隊(duì)列
- 方案四:訂閱mysql的binlog
- 總結(jié)
緩存一致性問題
通常情況下,我們使用緩存的主要目的是為了提升查詢的性能。大多數(shù)情況下,是這樣使用緩存的:
當(dāng)數(shù)據(jù)庫有數(shù)據(jù)更新時(shí),在很長的一段時(shí)間內(nèi)(決定于緩存的過期時(shí)間),用戶請求從緩存中獲取到的都可能是舊值,而非數(shù)據(jù)庫的最新值。那么,該如何更新緩存呢?目前有以下四種解決方案:
-
先更新緩存,再更新數(shù)據(jù)庫(差)
-
先更新數(shù)據(jù)庫,再更新緩存(差)
-
先刪除緩存,后更新數(shù)據(jù)庫(一般)
-
先更新數(shù)據(jù)庫,后刪除緩存(推薦)
討論四種方案前先統(tǒng)一兩個(gè)認(rèn)知,以便更好理解四種方案:
- 緩存一致性問題沒有絕對可靠的方案,我們只能讓兩者盡量接近,但無論如何也不能百分百達(dá)到一致性效果。
- 緩存和數(shù)據(jù)庫,無論先處理誰,只要后者有延遲/失敗,都會(huì)導(dǎo)致不一致的情況,這也正是緩存不一致的根本原因所在。所有解決方案和討論都是圍繞這一點(diǎn)來進(jìn)行的。
1、先更新緩存,再更新DB
缺點(diǎn):如果剛寫完緩存,突然網(wǎng)絡(luò)出現(xiàn)了異常,導(dǎo)致寫數(shù)據(jù)庫失敗了。這樣緩存中的數(shù)據(jù)就變成臟數(shù)據(jù),這個(gè)問題非常嚴(yán)重,也是最差的一種解決
方案二:先更新DB,再更新緩存
缺點(diǎn)一:問題又來了,寫數(shù)據(jù)庫成功,但寫緩存失敗了,依然會(huì)造成緩存臟數(shù)據(jù)的問題。但寫緩存失敗比寫數(shù)據(jù)庫失敗的概率要小很多了(因?yàn)閿?shù)據(jù)庫可能有加鎖、外鍵約束、超時(shí)等機(jī)制限制),所以此方案要比第一種方案好一點(diǎn)。
- 如果對接口性能要求不高,還可以把寫數(shù)據(jù)庫和寫緩存放到一個(gè)事務(wù)中,寫緩存失敗就回滾數(shù)據(jù)庫。
缺點(diǎn)二:然而高并發(fā)場景下,還會(huì)有個(gè)棘手問題:
- 請求a先過來,剛寫完了數(shù)據(jù)庫。但由于網(wǎng)絡(luò)原因,卡頓了一下,還沒來得及寫緩存。
- 這時(shí)候請求b過來了,先寫了數(shù)據(jù)庫。
- 接下來,請求b順利寫了緩存。
- 此時(shí),請求a卡頓結(jié)束,也寫了緩存。
很顯然,在這個(gè)過程當(dāng)中,請求b在緩存中的新數(shù)據(jù),被請求a的舊數(shù)據(jù)覆蓋了。
也就是說:在高并發(fā)場景中,如果多個(gè)線程同時(shí)執(zhí)行先寫數(shù)據(jù)庫,再寫緩存的操作,可能會(huì)出現(xiàn)數(shù)據(jù)庫是新值,而緩存中是舊值,兩邊數(shù)據(jù)不一致的情況。
缺點(diǎn)三:浪費(fèi)系統(tǒng)資源
- 寫的緩存的內(nèi)容,并不是簡單的數(shù)據(jù),而是要經(jīng)過非常復(fù)雜的計(jì)算或者查詢篩選得出的結(jié)果,這樣每寫一次緩存都要計(jì)算一次,這是非常浪費(fèi)系統(tǒng)資源的,尤其對那些寫多讀少的業(yè)務(wù)場景,更是雪上加霜。
刪除緩存類
方案三:先刪緩存,再寫數(shù)據(jù)庫
方案一:
嗯,看起來還不錯(cuò)。即使寫數(shù)據(jù)庫失敗了,下個(gè)請求也會(huì)重新觸發(fā)寫緩存操作,基本上避免更新緩存的所有弊端,然而也不是十全十美。
缺點(diǎn):
- 請求d先過來,把緩存刪除了。但由于網(wǎng)絡(luò)原因,卡頓了一下,還沒來得及寫數(shù)據(jù)庫。
- 這時(shí)請求c過來了,先查緩存發(fā)現(xiàn)沒數(shù)據(jù),再查數(shù)據(jù)庫,有數(shù)據(jù),但是舊值。
- 請求c將數(shù)據(jù)庫中的舊值,更新到緩存中。
- 此時(shí),請求d卡頓結(jié)束,把新值寫入數(shù)據(jù)庫。
這種極端情況下依然會(huì)導(dǎo)致寫入的緩存為舊值。
推薦1:延遲雙刪
為了避免方案1的避免,寫完數(shù)據(jù)庫后,再刪除一次。
該方案有個(gè)非常關(guān)鍵的地方是:第二次刪除緩存,并非立馬就刪,而是要在一定的時(shí)間間隔之后。
sleep的時(shí)間要對業(yè)務(wù)讀寫緩存的時(shí)間做出評估,sleep時(shí)間大于讀寫緩存的時(shí)間即可。
那么,為什么一定要間隔一段時(shí)間之后,才能刪除緩存呢?
請求d卡頓結(jié)束,把新值寫入數(shù)據(jù)庫后,請求c將數(shù)據(jù)庫中的舊值,更新到緩存中。此時(shí),如果請求d刪除太快,在請求c將數(shù)據(jù)庫中的舊值更新到緩存之前,就已經(jīng)把緩存刪除了,這次刪除就沒任何意義。必須要在請求c更新緩存之后,再刪除緩存,才能把舊值及時(shí)刪除了。
還是mysql讀寫分離的問題
方案四:先寫數(shù)據(jù)庫,再刪緩存
就怕一種情況:緩存失效。
- 緩存自動(dòng)失效。
- 請求f查詢緩存,發(fā)緩存中沒有數(shù)據(jù),查詢數(shù)據(jù)庫的舊值,但由于網(wǎng)絡(luò)原因卡頓了,沒有來得及更新緩存。
- 請求e先寫數(shù)據(jù)庫,接著刪除了緩存。
- 請求f更新舊值到緩存中。
這時(shí),緩存和數(shù)據(jù)庫的數(shù)據(jù)同樣出現(xiàn)不一致的情況了。但這種情況還是比較少的,需要同時(shí)滿足以下條件:
- 緩存剛好自動(dòng)失效。
- 請求f從數(shù)據(jù)庫查出舊值,更新緩存的耗時(shí),比請求e寫數(shù)據(jù)庫,并且刪除緩存的耗時(shí)還長。
緩存失效。只有可能是查詢比刪除慢的情況,而這種情況相對來說會(huì)少很多。同時(shí)結(jié)合延時(shí)雙刪的處理,可以有效的避免緩存不一致的情況。
刪除緩存失敗怎么辦?
其實(shí)先寫數(shù)據(jù)庫,再刪緩存的方案,跟緩存雙刪的方案一樣,有一個(gè)共同的風(fēng)險(xiǎn)點(diǎn),即:如果緩存刪除失敗了怎么辦?
方案一:設(shè)置過期時(shí)間
緩存設(shè)置一個(gè)過期時(shí)間,比如5分鐘。當(dāng)然這種方案只適合數(shù)據(jù)更新不是太頻繁的業(yè)務(wù)。
方案二:同步重試
在接口中判斷是否刪除成功,如果失敗就重試,直到成功或超過最大重試次數(shù)為止,返回?cái)?shù)據(jù)。當(dāng)然,這種方案的缺點(diǎn)就是可能影響接口性能。
方案三:消息隊(duì)列
將刪除緩存任務(wù)寫入mq等消息中間件中,在mq的consumer中處理。但問題也很多:
引入消息中間件之后,問題更復(fù)雜了,對業(yè)務(wù)代碼有一定侵入性、消息丟失怎么辦
消息本身的延遲也會(huì)帶來短暫的不一致性,不過這個(gè)延遲相對來說還是可以接受的
比如基于 RocketMQ 的可靠性消息通信,來實(shí)現(xiàn)最終一致性。
方案四:訂閱mysql的binlog
當(dāng)一條數(shù)據(jù)發(fā)生修改時(shí),MySQL 就會(huì)產(chǎn)生一條變更日志(binlog),我們可以訂閱這個(gè)日志,拿到具體操作的數(shù)據(jù),然后再根據(jù)這條數(shù)據(jù),去刪除對應(yīng)的緩存。
意思就是我們的業(yè)務(wù)應(yīng)用在修改數(shù)據(jù)時(shí),「只需」修改數(shù)據(jù)庫,無需操作緩存。步驟如下:
- 在業(yè)務(wù)接口中寫數(shù)據(jù)庫之后,直接返回成功。
- mysql服務(wù)器會(huì)自動(dòng)把變更的數(shù)據(jù)寫入binlog中。
- binlog訂閱者(消費(fèi)者)獲取變更的數(shù)據(jù),然后刪除緩存
總結(jié)
緩存刪除比更新效果更好
舉個(gè)例子:如果數(shù)據(jù)庫1小時(shí)內(nèi)更新了1000次,那么緩存也要更新1000次,但是這個(gè)緩存可能只在最后一次更新后被讀取了1次,那么前999次的更新有必要嗎?
反過來,如果是刪除的話,就算數(shù)據(jù)庫更新了1000次,那么也只是做了1次緩存刪除(刪除前判斷key是否存在),只有當(dāng)緩存真正被讀取的時(shí)候才去數(shù)據(jù)庫加載
刪除緩存有兩種方式:
- 先刪除緩存,再更新數(shù)據(jù)庫。解決方案是使用延遲雙刪。
- 先更新數(shù)據(jù)庫,再刪除緩存。解決方案是消息隊(duì)列或者監(jiān)聽binlog同步,引入消息隊(duì)列會(huì)帶來更多的問題,對業(yè)務(wù)代碼有一定侵入性,并不推薦直接使用。
針對緩存一致性要求不是很高的場景,那么只通過設(shè)置超時(shí)時(shí)間就可以了。