當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 小林coding
[導(dǎo)讀]大家好,我是小林。上一周我寫一了篇,數(shù)據(jù)庫(kù)和緩存雙寫一致性的文章「老板真愛畫大餅!」,故事的主人公是程序員阿旺。當(dāng)時(shí)只寫了上半篇,看到很多小伙伴催更下篇,說(shuō)來(lái)就來(lái)!前情回顧上回程序員阿旺為了提升數(shù)據(jù)訪問的性能,引入Redis作為MySQL緩存層,但是這件事情并不是那么簡(jiǎn)單,因?yàn)檫€...

大家好,我是小林。上一周我寫一了篇,數(shù)據(jù)庫(kù)和緩存雙寫一致性的文章「老板真愛畫大餅!」,故事的主人公是程序員阿旺。當(dāng)時(shí)只寫了上半篇,看到很多小伙伴催更下篇,說(shuō)來(lái)就來(lái)!

前情回顧

上回程序員阿旺為了提升數(shù)據(jù)訪問的性能,引入 Redis 作為 MySQL 緩存層,但是這件事情并不是那么簡(jiǎn)單,因?yàn)檫€要考慮 Redis 和 MySQL 雙寫一致性的問題。阿旺經(jīng)過一番周折,最終選用了「先更新數(shù)據(jù)庫(kù),再刪緩存」的策略,原因是這個(gè)策略即使在并發(fā)讀寫時(shí),也能最大程度保證數(shù)據(jù)一致性。聰明的阿旺還搞了個(gè)兜底的方案,就是給緩存加上了過期時(shí)間。本以為就這樣不會(huì)在出現(xiàn)數(shù)據(jù)一致性的問題,結(jié)果將功能上線后,老板還是收到用戶的投訴「說(shuō)自己明明更新了數(shù)據(jù),但是數(shù)據(jù)要過一段時(shí)間才生效」,客戶接受不了。老板轉(zhuǎn)告給了阿旺,阿旺得知又有 Bug 就更慌了,立馬就登錄服務(wù)器去排查問題,查看日志后得知了原因?!赶雀聰?shù)據(jù)庫(kù), 再刪除緩存」其實(shí)是兩個(gè)操作,這次客戶投訴的問題就在于,在刪除緩存(第二個(gè)操作)的時(shí)候失敗了,導(dǎo)致緩存中的數(shù)據(jù)是舊值,而數(shù)據(jù)庫(kù)是最新值。好在之前給緩存加上了過期時(shí)間,所以才會(huì)出現(xiàn)客戶說(shuō)的過一段時(shí)間才更新生效的現(xiàn)象,假設(shè)如果沒有這個(gè)過期時(shí)間的兜底,那后續(xù)的請(qǐng)求讀到的就會(huì)一直是緩存中的舊數(shù)據(jù),這樣問題就更大了。所以新的問題來(lái)了,如何保證「先更新數(shù)據(jù)庫(kù) ,再刪除緩存」這兩個(gè)操作能執(zhí)行成功?阿旺分析出問題后,慌慌張張的向老板匯報(bào)了問題。老板知道事情后,又給了阿旺幾天來(lái)解決這個(gè)問題,畫餅的事情這次沒有再提了。
  • 阿旺會(huì)用什么方式來(lái)解決這個(gè)問題呢?

  • 老板畫的餅事情,能否兌現(xiàn)給阿旺呢?

如何保證兩個(gè)操作都能執(zhí)行成功?

這次用戶的投訴是因?yàn)樵趧h除緩存(第二個(gè)操作)的時(shí)候失敗了,導(dǎo)致緩存還是舊值,而數(shù)據(jù)庫(kù)是最新值,造成數(shù)據(jù)庫(kù)和緩存數(shù)據(jù)不一致的問題,會(huì)對(duì)敏感業(yè)務(wù)造成影響。舉個(gè)例子,來(lái)說(shuō)明下。應(yīng)用要把數(shù)據(jù) X 的值從 1 更新為 2,先成功更新了數(shù)據(jù)庫(kù),然后在 Redis 緩存中刪除 X 的緩存,但是這個(gè)操作卻失敗了,這個(gè)時(shí)候數(shù)據(jù)庫(kù)中 X 的新值為 2,Redis 中的 X 的緩存值為 1,出現(xiàn)了數(shù)據(jù)庫(kù)和緩存數(shù)據(jù)不一致的問題。
那么,后續(xù)有訪問數(shù)據(jù) X 的請(qǐng)求,會(huì)先在 Redis 中查詢,因?yàn)榫彺娌]有 誒刪除,所以會(huì)緩存命中,但是讀到的卻是舊值 1。其實(shí)不管是先操作數(shù)據(jù)庫(kù),還是先操作緩存,只要第二個(gè)操作失敗都會(huì)出現(xiàn)數(shù)據(jù)一致的問題。問題原因知道了,該怎么解決呢?有兩種方法:
  • 重試機(jī)制。

  • 訂閱 MySQL binlog,再操作緩存。

先來(lái)說(shuō)第一種。
重試機(jī)制
我們可以引入消息隊(duì)列,將第二個(gè)操作(刪除緩存)要操作的數(shù)據(jù)加入到消息隊(duì)列,由消費(fèi)者來(lái)操作數(shù)據(jù)。
  • 如果應(yīng)用刪除緩存失敗,可以從消息隊(duì)列中重新讀取數(shù)據(jù),然后再次刪除緩存,這個(gè)就是重試機(jī)制。當(dāng)然,如果重試超過的一定次數(shù),還是沒有成功,我們就需要向業(yè)務(wù)層發(fā)送報(bào)錯(cuò)信息了。

  • 如果刪除緩存成功,就要把數(shù)據(jù)從消息隊(duì)列中移除,避免重復(fù)操作,否則就繼續(xù)重試。

舉個(gè)例子,來(lái)說(shuō)明重試機(jī)制的過程。
訂閱 MySQL binlog,再操作緩存
先更新數(shù)據(jù)庫(kù),再刪緩存」的策略的第一步是更新數(shù)據(jù)庫(kù),那么更新數(shù)據(jù)庫(kù)成功,就會(huì)產(chǎn)生一條變更日志,記錄在 binlog 里。于是我們就可以通過訂閱 binlog 日志,拿到具體要操作的數(shù)據(jù),然后再執(zhí)行緩存刪除,阿里巴巴開源的 Canal 中間件就是基于這個(gè)實(shí)現(xiàn)的。Canal 模擬 MySQL 主從復(fù)制的交互協(xié)議,把自己偽裝成一個(gè) MySQL 的從節(jié)點(diǎn),向 MySQL 主節(jié)點(diǎn)發(fā)送 dump 請(qǐng)求,MySQL 收到請(qǐng)求后,就會(huì)開始推送 Binlog 給 Canal,Canal 解析 Binlog 字節(jié)流之后,轉(zhuǎn)換為便于讀取的結(jié)構(gòu)化數(shù)據(jù),供下游程序訂閱使用。下圖是 Canal 的工作原理:
所以,如果要想保證「先更新數(shù)據(jù)庫(kù),再刪緩存」策略第二個(gè)操作能執(zhí)行成功,我們可以使用「消息隊(duì)列來(lái)重試緩存的刪除」,或者「訂閱 MySQL binlog 再操作緩存」,這兩種方法有一個(gè)共同的特點(diǎn),都是采用異步操作緩存。

老板發(fā)餅啦

阿旺由于對(duì)消息隊(duì)列比較熟悉,所以他決定采用「消息隊(duì)列來(lái)重試緩存的刪除」的方案,來(lái)解決這次的用戶問題。經(jīng)過幾天幾夜的操作,服務(wù)器搞定啦,立馬向老板匯報(bào)工作。老板讓阿旺再觀察些時(shí)間,如果沒問題,到中秋節(jié)就商量“餅”的事情。時(shí)間過的很快,中秋佳節(jié)到了,這期間一直都沒有用戶反饋數(shù)據(jù)不一致的問題。老板見這次阿旺表現(xiàn)很好,沒有再出現(xiàn)任何差錯(cuò),服務(wù)器的訪問性能也上來(lái)了,于是給阿旺發(fā)了這個(gè)超級(jí)大的月餅,你看這個(gè)餅又大又圓,就像你的代碼又長(zhǎng)又多。阿旺看到這個(gè)月餅,哭笑不得,沒想到這就是老板畫的餅,是真的很大餅。。。。以上故事純屬虛擬,如有巧合,以你為準(zhǔn)。好了,今天中秋,大家中秋節(jié)快樂啦!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉