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

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

前情回顧

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

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

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

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

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

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

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

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

老板發(fā)餅啦

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

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