緩存與數(shù)據(jù)庫的一致性問題深入解析

本文轉(zhuǎn)載自后端技術(shù)漫談,原文鏈接 https://mp.weixin.qq.com/s/-0_ReIv2bp5snq3NUI3P7A年缎,文章內(nèi)容有部分刪減。

當(dāng)我們在做數(shù)據(jù)庫與緩存數(shù)據(jù)同步時惭墓,究竟更新緩存涡贱,還是刪除緩存,究竟是先操作數(shù)據(jù)庫秦士,還是先操作緩存频鉴?本文帶大家深度分析數(shù)據(jù)庫與緩存的雙寫問題栓辜,并且給出了所有方案的實現(xiàn)代碼方便大家參考。

不更新緩存垛孔,而是刪除緩存

大部分觀點認(rèn)為藕甩,做緩存不應(yīng)該是去更新緩存,而是應(yīng)該刪除緩存似炎,然后由下個請求去去緩存辛萍,發(fā)現(xiàn)不存在后再讀取數(shù)據(jù)庫,寫入緩存羡藐。

觀點引用:《分布式之?dāng)?shù)據(jù)庫和緩存雙寫一致性方案解析》孤獨煙

原因一:線程安全角度

同時有請求A和請求B進行更新操作贩毕,那么會出現(xiàn)

(1)線程A更新了數(shù)據(jù)庫

(2)線程B更新了數(shù)據(jù)庫

(3)線程B更新了緩存

(4)線程A更新了緩存

這就出現(xiàn)請求A更新緩存應(yīng)該比請求B更新緩存早才對,但是因為網(wǎng)絡(luò)等原因仆嗦,B卻比A更早更新了緩存辉阶。這就導(dǎo)致了臟數(shù)據(jù),因此不考慮瘩扼。

原因二:業(yè)務(wù)場景角度

有如下兩點:

(1)如果你是一個寫數(shù)據(jù)庫場景比較多谆甜,而讀數(shù)據(jù)場景比較少的業(yè)務(wù)需求,采用這種方案就會導(dǎo)致集绰,數(shù)據(jù)壓根還沒讀到规辱,緩存就被頻繁的更新,浪費性能栽燕。

(2)如果你寫入數(shù)據(jù)庫的值罕袋,并不是直接寫入緩存的改淑,而是要經(jīng)過一系列復(fù)雜的計算再寫入緩存。那么浴讯,每次寫入數(shù)據(jù)庫后朵夏,都再次計算寫入緩存的值,無疑是浪費性能的榆纽。顯然仰猖,刪除緩存更為適合。

其實如果業(yè)務(wù)非常簡單奈籽,只是去數(shù)據(jù)庫拿一個值饥侵,寫入緩存,那么更新緩存也是可以的唠摹。但是爆捞,淘汰緩存操作簡單奉瘤,并且?guī)淼母弊饔弥皇窃黾恿艘淮蝐ache miss勾拉,建議作為通用的處理方式。

先操作緩存盗温,還是先操作數(shù)據(jù)庫藕赞?

那么問題就來了,我們是先刪除緩存卖局,然后再更新數(shù)據(jù)庫斧蜕,還是先更新數(shù)據(jù)庫,再刪緩存呢砚偶?

先來看看大佬們怎么說批销。《【58沈劍架構(gòu)系列】緩存架構(gòu)設(shè)計細(xì)節(jié)二三事》58沈劍:

對于一個不能保證事務(wù)性的操作染坯,一定涉及“哪個任務(wù)先做均芽,哪個任務(wù)后做”的問題,解決這個問題的方向是:如果出現(xiàn)不一致单鹿,誰先做對業(yè)務(wù)的影響較小掀宋,就誰先執(zhí)行。

假設(shè)先淘汰緩存仲锄,再寫數(shù)據(jù)庫:第一步淘汰緩存成功劲妙,第二步寫數(shù)據(jù)庫失敗,則只會引發(fā)一次Cache miss儒喊。

假設(shè)先寫數(shù)據(jù)庫镣奋,再淘汰緩存:第一步寫數(shù)據(jù)庫操作成功,第二步淘汰緩存失敗怀愧,則會出現(xiàn)DB中是新數(shù)據(jù)侨颈,Cache中是舊數(shù)據(jù)富雅,數(shù)據(jù)不一致。

沈劍老師說的沒有問題肛搬,不過沒完全考慮好并發(fā)請求時的數(shù)據(jù)臟讀問題没佑,讓我們再來看看孤獨煙老師《分布式之?dāng)?shù)據(jù)庫和緩存雙寫一致性方案解析》:

先刪緩存,再更新數(shù)據(jù)庫

該方案會導(dǎo)致請求數(shù)據(jù)不一致温赔。假設(shè)同時有一個請求A進行更新操作蛤奢,另一個請求B進行查詢操作。那么會出現(xiàn)如下情形:

(1)請求A進行寫操作陶贼,刪除緩存

(2)請求B查詢發(fā)現(xiàn)緩存不存在

(3)請求B去數(shù)據(jù)庫查詢得到舊值

(4)請求B將舊值寫入緩存

(5)請求A將新值寫入數(shù)據(jù)庫

上述情況就會導(dǎo)致不一致的情形出現(xiàn)啤贩。而且,如果不采用給緩存設(shè)置過期時間策略拜秧,該數(shù)據(jù)永遠(yuǎn)都是臟數(shù)據(jù)痹屹。

所以先刪緩存,再更新數(shù)據(jù)庫并不是一勞永逸的解決方案枉氮,再看看先更新數(shù)據(jù)庫志衍,再刪緩存這種方案怎么樣?

先更新數(shù)據(jù)庫聊替,再刪緩存這種情況不存在并發(fā)問題么楼肪?

不是的。假設(shè)同時有一個請求A做查詢操作惹悄,一個請求B做更新操作春叫,那么會有如下情形產(chǎn)生

(1)緩存剛好失效

(2)請求A查詢數(shù)據(jù)庫,得一個舊值

(3)請求B將新值寫入數(shù)據(jù)庫

(4)請求B刪除緩存

(5)請求A將查到的舊值寫入緩存

ok泣港,如果發(fā)生上述情況暂殖,確實是會發(fā)生臟數(shù)據(jù)。

然而当纱,發(fā)生這種情況的概率又有多少呢呛每?

發(fā)生上述情況有一個先天性條件,就是步驟(3)的寫數(shù)據(jù)庫操作比步驟(2)的讀數(shù)據(jù)庫操作耗時更短惫东,才有可能使得步驟(4)先于步驟(5)莉给。可是廉沮,大家想想颓遏,數(shù)據(jù)庫的讀操作的速度遠(yuǎn)快于寫操作的(不然做讀寫分離干嘛,做讀寫分離的意義就是因為讀操作比較快滞时,耗資源少)叁幢,因此步驟(3)耗時比步驟(2)更短,這一情形很難出現(xiàn)坪稽。

先更新數(shù)據(jù)庫曼玩,再刪緩存依然會有問題鳞骤,不過,問題出現(xiàn)的可能性會因為上面說的原因黍判,變得比較低豫尽!

所以,如果你想實現(xiàn)基礎(chǔ)的緩存數(shù)據(jù)庫雙寫一致的邏輯顷帖,那么在大多數(shù)情況下美旧,在不想做過多設(shè)計,增加太大工作量的情況下贬墩,請先更新數(shù)據(jù)庫榴嗅,再刪緩存!

非要保證數(shù)據(jù)庫和緩存數(shù)據(jù)強一致性該怎么辦?

那么陶舞,如果我非要保證絕對一致性怎么辦嗽测,先給出結(jié)論:

沒有辦法做到絕對的一致性,這是由CAP理論決定的肿孵,緩存系統(tǒng)適用的場景就是非強一致性的場景唠粥,所以它屬于CAP中的AP。

CAP 定理又被稱作布魯爾定理颁井,厅贪,它指出對于一個分布式計算系統(tǒng)來說,不可能同時滿足以下三點:

  • 一致性(Consistency)(等同于所有節(jié)點訪問同一份最新的數(shù)據(jù)副本)
  • 可用性(Availability)(每次請求都能獲取到非錯的相應(yīng)——但是不保證獲取的數(shù)據(jù)為最新數(shù)據(jù))
  • 分區(qū)容錯性(Partition tolerance)(以實際效果而言雅宾,分區(qū)相當(dāng)于對通信的時限要求。系統(tǒng)如果不能在時限內(nèi)達成數(shù)據(jù)一致性葵硕,就意味著發(fā)生了分區(qū)的情況眉抬,必須就當(dāng)前操作在C和A之間做出選擇。)

所以懈凹,我們得委曲求全蜀变,可以去做到BASE理論中說的最終一致性

最終一致性強調(diào)的是系統(tǒng)中所有的數(shù)據(jù)副本介评,在經(jīng)過一段時間的同步后库北,最終能夠達到一個一致的狀態(tài)。因此们陆,最終一致性的本質(zhì)是需要系統(tǒng)保證最終數(shù)據(jù)能夠達到一致寒瓦,而不需要實時保證系統(tǒng)數(shù)據(jù)的強一致性

大佬們給出了到達最終一致性的解決思路,主要是針對上面兩種雙寫策略(先刪緩存坪仇,再更新數(shù)據(jù)庫/先更新數(shù)據(jù)庫杂腰,再刪緩存)導(dǎo)致的臟數(shù)據(jù)問題,進行相應(yīng)的處理椅文,來保證最終一致性喂很。

緩存延時雙刪

問:先刪除緩存惜颇,再更新數(shù)據(jù)庫中如何避免臟數(shù)據(jù)?

答:采用延時雙刪策略少辣。

上文我們提到凌摄,在先刪除緩存,再更新數(shù)據(jù)庫的情況下漓帅,如果不給緩存設(shè)置過期時間望伦,那么該數(shù)據(jù)永遠(yuǎn)都是臟數(shù)據(jù)。

那么延時雙刪怎么解決這個問題呢煎殷?

(1)先淘汰緩存

(2)再寫數(shù)據(jù)庫(這兩步和原來一樣)

(3)休眠1秒屯伞,再次淘汰緩存

這么做,可以將1秒內(nèi)所造成的緩存臟數(shù)據(jù)豪直,再次刪除劣摇。

那么,這個1秒怎么確定的弓乙,具體該休眠多久呢末融?

針對上面的情形,讀者應(yīng)該自行評估自己的項目的讀數(shù)據(jù)業(yè)務(wù)邏輯的耗時暇韧。然后寫數(shù)據(jù)的休眠時間則在讀數(shù)據(jù)業(yè)務(wù)邏輯的耗時基礎(chǔ)上勾习,加幾百ms即可。這么做的目的懈玻,就是確保讀請求結(jié)束巧婶,寫請求可以刪除讀請求造成的緩存臟數(shù)據(jù)。

如果你用了mysql的讀寫分離架構(gòu)怎么辦涂乌?

ok艺栈,在這種情況下,造成數(shù)據(jù)不一致的原因如下湾盒,還是兩個請求湿右,一個請求A進行更新操作,另一個請求B進行查詢操作罚勾。

(1)請求A進行寫操作毅人,刪除緩存

(2)請求A將數(shù)據(jù)寫入數(shù)據(jù)庫了,

(3)請求B查詢緩存發(fā)現(xiàn)尖殃,緩存沒有值

(4)請求B去從庫查詢丈莺,這時,還沒有完成主從同步分衫,因此查詢到的是舊值

(5)請求B將舊值寫入緩存

(6)數(shù)據(jù)庫完成主從同步场刑,從庫變?yōu)樾轮?/p>

上述情形,就是數(shù)據(jù)不一致的原因。還是使用雙刪延時策略牵现。只是铐懊,睡眠時間修改為在主從同步的延時時間基礎(chǔ)上,加幾百ms瞎疼。

采用這種同步淘汰策略科乎,吞吐量降低怎么辦?

ok贼急,那就將第二次刪除作為異步的茅茂。自己起一個線程,異步刪除太抓。這樣空闲,寫的請求就不用沉睡一段時間后了,再返回走敌。這么做碴倾,加大吞吐量。

所以在先刪除緩存掉丽,再更新數(shù)據(jù)庫的情況下跌榔,可以使用延時雙刪的策略,來保證臟數(shù)據(jù)只會存活一段時間捶障,就會被準(zhǔn)確的數(shù)據(jù)覆蓋僧须。

在先更新數(shù)據(jù)庫,再刪緩存的情況下项炼,緩存出現(xiàn)臟數(shù)據(jù)的情況雖然可能性極小担平,但也會出現(xiàn)。我們依然可以用延時雙刪策略芥挣,在請求A對緩存寫入了臟的舊值之后驱闷,再次刪除緩存。來保證去掉臟緩存空免。

刪緩存失敗了怎么辦:重試機制

看似問題都已經(jīng)解決了,但其實盆耽,還有一個問題沒有考慮到蹋砚,那就是刪除緩存的操作,失敗了怎么辦摄杂?比如延時雙刪的時候坝咐,第二次緩存刪除失敗了,那不還是沒有清除臟數(shù)據(jù)嗎析恢?

解決方案就是再加上一個重試機制墨坚,保證刪除緩存成功。

參考孤獨煙老師給的方案圖:

方案一:

流程如下所示

(1)更新數(shù)據(jù)庫數(shù)據(jù)映挂;

(2)緩存因為種種問題刪除失敗

(3)將需要刪除的key發(fā)送至消息隊列

(4)自己消費消息泽篮,獲得需要刪除的key

(5)繼續(xù)重試刪除操作盗尸,直到成功

然而,該方案有一個缺點帽撑,對業(yè)務(wù)線代碼造成大量的侵入泼各。于是有了方案二,在方案二中亏拉,啟動一個訂閱程序去訂閱數(shù)據(jù)庫的binlog扣蜻,獲得需要操作的數(shù)據(jù)。在應(yīng)用程序中及塘,另起一段程序莽使,獲得這個訂閱程序傳來的信息,進行刪除緩存操作笙僚。

方案二:

流程如下圖所示:

(1)更新數(shù)據(jù)庫數(shù)據(jù)

(2)數(shù)據(jù)庫會將操作信息寫入binlog日志當(dāng)中

(3)訂閱程序提取出所需要的數(shù)據(jù)以及key

(4)另起一段非業(yè)務(wù)代碼芳肌,獲得該信息

(5)嘗試刪除緩存操作,發(fā)現(xiàn)刪除失敗

(6)將這些信息發(fā)送至消息隊列

(7)重新從消息隊列中獲得該數(shù)據(jù)味咳,重試操作庇勃。

這里讀取binlog的中間件,可以采用阿里開源的canal槽驶。

好了责嚷,到這里我們已經(jīng)把緩存雙寫一致性的思路徹底梳理了一遍,下面就是我對這幾種思路徒手寫的實戰(zhàn)代碼掂铐,方便有需要的朋友參考罕拂。

小結(jié)

引用陳浩《緩存更新的套路》最后的總結(jié)語作為小結(jié):

分布式系統(tǒng)里要么通過2PC或是Paxos協(xié)議保證一致性,要么就是拼命的降低并發(fā)時臟數(shù)據(jù)的概率

緩存系統(tǒng)適用的場景就是非強一致性的場景全陨,所以它屬于CAP中的AP爆班,BASE理論。

異構(gòu)數(shù)據(jù)庫本來就沒辦法強一致辱姨,只是盡可能減少時間窗口柿菩,達到最終一致性

還有別忘了設(shè)置過期時間雨涛,這是個兜底方案枢舶。

文章內(nèi)容大致可以總結(jié)為如下幾點:

  • 對于讀多寫少的數(shù)據(jù),請使用緩存替久。
  • 為了保持?jǐn)?shù)據(jù)庫和緩存的一致性凉泄,會導(dǎo)致系統(tǒng)吞吐量的下降。
  • 為了保持?jǐn)?shù)據(jù)庫和緩存的一致性蚯根,會導(dǎo)致業(yè)務(wù)代碼邏輯復(fù)雜后众。
  • 緩存做不到絕對一致性,但可以做到最終一致性。
  • 對于需要保證緩存數(shù)據(jù)庫數(shù)據(jù)一致的情況蒂誉,請盡量考慮對一致性到底有多高要求教藻,選定合適的方案,避免過度設(shè)計拗盒。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怖竭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陡蝇,更是在濱河造成了極大的恐慌痊臭,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件登夫,死亡現(xiàn)場離奇詭異广匙,居然都是意外死亡,警方通過查閱死者的電腦和手機恼策,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門鸦致,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涣楷,你說我怎么就攤上這事分唾。” “怎么了狮斗?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵绽乔,是天一觀的道長。 經(jīng)常有香客問我碳褒,道長折砸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任沙峻,我火速辦了婚禮睦授,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摔寨。我一直安慰自己去枷,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布是复。 她就那樣靜靜地躺著沉填,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佑笋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天斑鼻,我揣著相機與錄音蒋纬,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛蜀备,可吹牛的內(nèi)容都是我干的关摇。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼碾阁,長吁一口氣:“原來是場噩夢啊……” “哼输虱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起脂凶,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤宪睹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蚕钦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亭病,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年嘶居,在試婚紗的時候發(fā)現(xiàn)自己被綠了罪帖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡邮屁,死狀恐怖整袁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佑吝,我是刑警寧澤坐昙,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站迹蛤,受9級特大地震影響民珍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盗飒,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一嚷量、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逆趣,春花似錦蝶溶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痕囱,卻和暖如春田轧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞍恢。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工傻粘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留每窖,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓弦悉,卻偏偏與公主長得像窒典,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子稽莉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361