兩次被裁之后,我終于解決了數(shù)據(jù)庫(kù)緩存一致性問(wèn)題
一
我是一名畢業(yè)兩年的程序員。
算上實(shí)習(xí),工作三年了,正是一個(gè)程序員的黃金時(shí)代,這讓我普通而自信。
但是從實(shí)習(xí)期,我就被辭退兩次了。
今天是我的又一場(chǎng)面試,而且是大廠面試。我要一洗前恥,證明自己。
好了,我要趕緊出發(fā),不然通往美好生活的996路公交車又堵了。
經(jīng)歷過(guò)西二旗的人潮人涌之后,我按時(shí)來(lái)到了面試官面前。
和面試官寒暄了幾句,他直接問(wèn)了一個(gè)技術(shù)問(wèn)題
"如果網(wǎng)站流量太高,我們通常會(huì)加緩存來(lái)減輕數(shù)據(jù)庫(kù)壓力,讀緩存很簡(jiǎn)單,如下圖

關(guān)于寫(xiě)緩存,你知道怎么設(shè)計(jì)這個(gè)方案,保證緩存與數(shù)據(jù)庫(kù)的數(shù)據(jù)一致性嗎?"
一剎那,往事涌上心頭,因?yàn)槲以谶@個(gè)問(wèn)題上,已經(jīng)栽了兩次了。
二
第一次是在實(shí)習(xí)期。
那年二十,剛剛工作,每日如嘍啰。
實(shí)習(xí)的公司是一家外包公司,沒(méi)有什么技術(shù)規(guī)范,按時(shí)上線是大家最重要的諾言。
我的第一個(gè)任務(wù)就是增加緩存,降低Mysql的壓力。
這個(gè)任務(wù)最核心的就是寫(xiě)緩存時(shí)怎么保證緩存和數(shù)據(jù)庫(kù)的一致性,當(dāng)年還是實(shí)習(xí)生的我顯然沒(méi)有意識(shí)到這個(gè)需求的復(fù)雜性,直接采用的方案就是
先更新數(shù)據(jù)庫(kù),再更新緩存
上線第二天,網(wǎng)站就出了Bug,我就被甲方爸爸投訴了。
后來(lái),在復(fù)盤(pán)中我才發(fā)現(xiàn),網(wǎng)站掛了的原因是:
如果同時(shí)有請(qǐng)求A和請(qǐng)求B進(jìn)行更新操作,那么會(huì)出現(xiàn)

請(qǐng)求B是最后請(qǐng)求的,那么應(yīng)該是他最后更新緩存為正確的數(shù)據(jù),但是有可能請(qǐng)求A處理的更慢,所以請(qǐng)求A更新了最后的緩存。
另外這個(gè)系統(tǒng)寫(xiě)數(shù)據(jù)庫(kù)場(chǎng)景比較多,而讀請(qǐng)求比較少,這種方案就導(dǎo)致數(shù)據(jù)壓根還沒(méi)讀到,緩存就被頻繁的更新,浪費(fèi)性能。
然后我當(dāng)天就被辭退了,理由是在辦公室工位吃螺螄粉。
三
沒(méi)想到啊沒(méi)想到,在這個(gè)問(wèn)題上,我還能梅開(kāi)二度。
畢業(yè)之后進(jìn)入的第一家公司,兢兢業(yè)業(yè)兩年半,業(yè)務(wù)量也逐漸上來(lái)了。
訪問(wèn)量上升,代表著我的薪水也有機(jī)會(huì)上升。我立馬做了個(gè)方案,準(zhǔn)備在一向不看好我的經(jīng)理面前表現(xiàn)一把。
核心邏輯就是采用
先刪緩存,再更新數(shù)據(jù)庫(kù)
經(jīng)理看完方案,直接畫(huà)了下面一個(gè)圖

淡淡說(shuō)道:"這樣的話,緩存都是臟數(shù)據(jù)了。"
我想了下,說(shuō):"確實(shí),不過(guò)可以雙刪緩存。"
public void write(String key,Object data){
redis.delKey(key); // 刪緩存
db.updateData(data); //更新數(shù)據(jù)庫(kù)
Thread.sleep(1000); // 根據(jù)業(yè)務(wù)執(zhí)行時(shí)間確定具體的時(shí)間
redis.delKey(key); // 我再刪緩存
}
經(jīng)理笑了笑,說(shuō):"我們可是采用了MySQL讀寫(xiě)分離架構(gòu)啊,如果有下面這樣兩個(gè)請(qǐng)求

還是會(huì)導(dǎo)致數(shù)據(jù)不一致啊!"
我有點(diǎn)不悅,這可是我主動(dòng)加班做的方案,一句贊賞都沒(méi)有,怎么老是被質(zhì)疑?
但還是回答道 "那就sleep時(shí)間修改為業(yè)務(wù)執(zhí)行時(shí)間+主從同步時(shí)間就可以了,就是等主從同步完了再刪一次。"
經(jīng)理又問(wèn)道:"嗯,可以。不過(guò)你這樣刪除緩存兩次,會(huì)造成吞吐量降低,怎么辦?"
我覺(jué)得很不爽了,又不是他開(kāi)發(fā)。
算了,我回應(yīng)到:"那就將第二次刪除改為異步的。即重新起一個(gè)線程,異步刪除。"
經(jīng)理又問(wèn)道:"那要是第二次刪除緩存失敗呢?"

我無(wú)奈了:"您說(shuō)呢,畢竟您也是經(jīng)理,要不您也說(shuō)兩個(gè)方案讓我學(xué)習(xí)一下?"
經(jīng)理笑了笑:"年輕人,路走窄了啊!"
然后我當(dāng)天就被辭退了,理由是我代碼縮進(jìn)用的Tab而非空格。
四
此刻,我坐在面試官前面,面對(duì)這個(gè)問(wèn)題已經(jīng)有三年了,我也早已胸有成竹。
想到這,我直接站了起來(lái),走到白板面前,說(shuō)道:"實(shí)不相瞞,前兩次我的離職都和這個(gè)問(wèn)題有關(guān),所以我也思考了很多,不如直接我就講講最優(yōu)解法。那就是
先更新數(shù)據(jù)庫(kù),再刪緩存
當(dāng)然,這樣也會(huì)有并發(fā)問(wèn)題,比如

但是數(shù)據(jù)庫(kù)讀操作速度遠(yuǎn)快于寫(xiě)操作,所以存在臟數(shù)據(jù)的可能性為0。
當(dāng)然如果您問(wèn),如果真的存在怎么辦?
簡(jiǎn)單,雙刪就行了,即第一次刪除緩存之后,等待一段時(shí)間重新再刪一次。
當(dāng)然您如果還問(wèn),刪除緩存失敗了怎么辦,解決方法如下

即引入消息隊(duì)列,刪除緩存失敗的記錄下來(lái)重復(fù)刪除,直到成功方可。如此一來(lái),萬(wàn)無(wú)一失。"
面試官點(diǎn)了點(diǎn)頭,鼓了鼓掌,嘆到:"優(yōu)秀,不過(guò)我好奇你前兩次的離職,可以和我講講嗎?"
面試官聽(tīng)完我的經(jīng)歷之后,對(duì)我深表同情,問(wèn)道:"假如我給了你offer,你走到之前那些開(kāi)除你的人面前,你會(huì)說(shuō)什么呢?"
我走到窗戶旁邊,望向遠(yuǎn)方,輕聲道:"我會(huì)走到他們面前,把offer甩給他們看,告訴他們,我等了三年,就是要等一個(gè)機(jī)會(huì),我要爭(zhēng)一口氣,不是想證明我了不起,我只是要告訴人家,我失去的東西一定要拿回來(lái)!"
參考資料
https://www.cnblogs.com/rjzheng/p/9041659.html
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
