聊聊缓存机制:双写兜兜转转,又回到了串行化
來源 |?moon聊技術(shù)
責(zé)編 | 寇雪芹
頭圖 | 下載于ICphoto
什么是雙寫?這個(gè)很好理解,雙寫就是說,一份數(shù)據(jù)在數(shù)據(jù)庫存一份,在緩存中也存一份,給緩存一個(gè)過期時(shí)間,當(dāng)讀不到緩存時(shí)從數(shù)據(jù)庫讀出來然后寫入緩存。
為什么需要雙寫呢?
當(dāng)請求量越來越大的時(shí)候,系統(tǒng)會慢慢出現(xiàn)瓶頸,由于數(shù)據(jù)庫的鏈接是有限的,無法支撐較高的QPS,所以我們要想一個(gè)辦法分擔(dān)數(shù)據(jù)庫的壓力,于是就有了雙寫,將數(shù)據(jù)寫入緩存,客戶端讀取數(shù)據(jù)直接從緩存中讀取,這樣就可以提高系統(tǒng)的性能。
但是如果要使用雙寫,那么不管是先更新緩存還是先更新mysql,總會有時(shí)間間隔,那么就要保證你的業(yè)務(wù)在一定程度上允許短暫的數(shù)據(jù)不一致的情況出現(xiàn),否則,還是不建議使用的。
那么就有人問了?雙寫一定不能保證強(qiáng)一致性嗎?
答案是可以,只要把所有與其相關(guān)的讀寫請求用隊(duì)列串行化,這樣就可以保證雙寫的強(qiáng)一致性了,但是這樣會極大的降低系統(tǒng)的QPS,非常不推薦這種做法。既然要雙寫,那么肯定會出現(xiàn)數(shù)據(jù)庫和緩存數(shù)據(jù)不一致的情況,要怎樣去避免呢?
雙寫不一致問題要怎么解決?
一.先更新數(shù)據(jù)庫,再更新緩存
這種情況會有什么問題呢?我們看下圖:
首先a先更新數(shù)據(jù)庫,按照正常流程來走,緊接著要a線程刪除緩存,可是突然后面來了個(gè)b線程,并且a線程因?yàn)楦鞣N業(yè)務(wù)原因卡住了,導(dǎo)致b線程先完成了,之后a線程才更新緩存。這時(shí)突然有其他線程進(jìn)來讀數(shù)據(jù),就會讀到a的數(shù)據(jù),但是按照業(yè)務(wù)流程來走,應(yīng)該讀到b的數(shù)據(jù),此時(shí),就出現(xiàn)了數(shù)據(jù)錯(cuò)亂的問題。
1.線程a更新數(shù)據(jù)庫
2.線程b更新數(shù)據(jù)庫
3.線程b更新緩存
4.線程a更新緩存
5.其他線程讀數(shù)據(jù)(讀錯(cuò)了)
到這里我們會發(fā)現(xiàn),直接更新緩存是有很大的問題的,而且很多時(shí)候,在復(fù)雜點(diǎn)的緩存場景,緩存不單單是數(shù)據(jù)庫中直接取出來的值,有可能是聯(lián)合其他的很多數(shù)據(jù)結(jié)合計(jì)算出來的一個(gè)值。
而且可能會有一種場景,我們經(jīng)常在更新數(shù)據(jù)庫后直接更新緩存,但是在此之間并沒有緩存被訪問的需求,這樣我們就做了很多無用功,付出了很多代價(jià)。
大家應(yīng)該對單例模式有所了解,其中有一種懶加載的思想,就是說,在你需要的時(shí)候再去加載,用在雙寫的情況下非常合適,也就有了下面這種先更新數(shù)據(jù)庫,再刪除緩存的模式。
二.先更新數(shù)據(jù)庫,再刪除緩存
這種情況又會有什么問題呢?
當(dāng)然,這還是一種有問題的方案,我們來跟著圖盤一盤。
1:線程a更新數(shù)據(jù)庫
2:程序掛了,沒來的及刪除緩存
3.其他線程來讀數(shù)據(jù)(全都是錯(cuò)的)
這種方案的問題一目了然,只要程序掛了,就會出現(xiàn)數(shù)據(jù)讀錯(cuò)的情況,真實(shí)的業(yè)務(wù)你是應(yīng)該讀到a線程的值,卻一直在讀之前的值。
那這種方案有沒有優(yōu)化呢?
當(dāng)然也有了,其實(shí)我們可以每次寫入都記錄日志,然后修改結(jié)束后也記錄日志,通過日志狀態(tài)來判斷是否寫入成功,
如果沒有寫入成功后續(xù)并且沒有新的寫入請求,就補(bǔ)寫,
否則不做處理。
但是這種情況也會出現(xiàn)不一致的問題,就是如果寫數(shù)據(jù)庫程序斷了,到下次恢復(fù)數(shù)據(jù)之前這段時(shí)間,還會出現(xiàn)數(shù)據(jù)不一致的情況。
并且如果是頻繁寫入的情況,很有可能日志機(jī)制沒有發(fā)揮作用,就有新數(shù)據(jù)寫入覆蓋,并且日志系統(tǒng)還要占用額外的資源。
我懂了!應(yīng)該先刪除緩存再更新數(shù)據(jù)庫,這樣就可以了!
三.先刪除緩存 再更新數(shù)據(jù)庫
來來來,繼續(xù)貼圖,是不是很熟悉?
這種方案會有問題嗎??當(dāng)然有,繼續(xù)盤道:
1:線程a刪除緩存
2:線程b刪除緩存
3:線程a卡了
4:線程b更新數(shù)據(jù)庫
5:線程a更新數(shù)據(jù)
6:其他線程讀數(shù)據(jù),讀到了a的(又錯(cuò)了)
完了,這種情況居然也有問題,線程a到底行不行,每次都是你出事。這種情況中間會有一段數(shù)據(jù)亂掉,但是隨著下次的更新數(shù)據(jù)還是會恢復(fù)正確。難道終極方案是先刪除緩存,再更新數(shù)據(jù)庫,再更新緩存??
四.先刪除緩存,再更新數(shù)據(jù)庫,再刪除緩存
繼續(xù)貼圖
1.線程a刪除緩存
2.其他線程讀取數(shù)據(jù),讀到的是a之前的數(shù)據(jù)
3.線程a更新數(shù)據(jù)庫
4.線程a刪除緩存
5.其他線程設(shè)置緩存數(shù)據(jù),是a之前的數(shù)據(jù)(此時(shí)應(yīng)該是a的)
大家是不是又發(fā)現(xiàn)了,這種設(shè)計(jì)方案還是會有問題的,直到下次數(shù)據(jù)更新才有可能將數(shù)據(jù)恢復(fù)正確。來吧,最后一種大家經(jīng)常討論的延時(shí)雙刪方案,我們一起盤一盤。
五.延時(shí)雙刪
go on
1.先刪除緩存
2.再寫數(shù)據(jù)庫
3.休眠一段時(shí)間(根據(jù)具體的業(yè)務(wù)時(shí)間來定)
4.再次刪除緩存
這里加了一個(gè)延時(shí)的操作,目的是確保 修改數(shù)據(jù)庫 -> 清空緩存前,其他事務(wù)的更改緩存操作已經(jīng)執(zhí)行完。所有的寫操作以數(shù)據(jù)庫為準(zhǔn),只要到達(dá)緩存過期時(shí)間,則后面的讀請求自然會從數(shù)據(jù)庫中讀取新值然后回填緩存。
但這其中難免還是會大量的查詢到舊緩存數(shù)據(jù)的,因?yàn)檠訒r(shí)時(shí)間是根據(jù)業(yè)務(wù)自己定義的,時(shí)間太長和太短在高并發(fā)情況下都會有查詢到臟數(shù)據(jù)的情況產(chǎn)生。這樣最差的情況就是在超時(shí)時(shí)間內(nèi)數(shù)據(jù)存在不一致。
結(jié)語
到這里大家應(yīng)該會發(fā)現(xiàn),除了串行化這種方式以外,其他無論哪種方式大大小小都會有數(shù)據(jù)不一致的現(xiàn)象發(fā)生,有時(shí)為了維護(hù)數(shù)據(jù)一致性問題還要做很多額外很重的操作,比如加一些日志來做狀態(tài)處理雙寫問題,具體的方案選擇還是要根據(jù)業(yè)務(wù)的敏感度來定的。
60+專家,13個(gè)技術(shù)領(lǐng)域,CSDN?《IT 人才成長路線圖》重磅來襲!
直接掃碼或微信搜索「CSDN」公眾號,后臺回復(fù)關(guān)鍵詞「路線圖」,即可獲取完整路線圖!
更多精彩推薦 ?5G、射頻、奧特曼,這仨有聯(lián)系嗎??再見 Nacos,我要玩 Service Mesh 了!?用根因定位法,讓運(yùn)維效率再高一點(diǎn)!點(diǎn)分享點(diǎn)收藏點(diǎn)點(diǎn)贊點(diǎn)在看總結(jié)
以上是生活随笔為你收集整理的聊聊缓存机制:双写兜兜转转,又回到了串行化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信“支付”页全国多地上线“出行服务”,
- 下一篇: 云原生与AI时代的存储该是什么样?新华三