緩存的雙寫一致性解決方案 解決redis與mysql數據一致性 看不懂的你來打我~

前言

此篇章爲分析如何將redis的緩存與mysql數據同步的解決方案,本人蔘照各路大神的解決方案匯聚而成,難免會有錯誤的地方,肯定各路大神評論區無情鞭撻~~

一. 何謂雙寫一致性

你只要用緩存,就可能會涉及到緩存與數據庫雙存儲雙寫,你只要是雙寫,就一定會有數據一致性的問題,那麼你如何解決一致性問題?

我想,已經有小夥伴有了好的方案了,爲什麼我們不能直接將其中讀請求與寫請求串行化,全部整到一個隊列中去消費,不就一定不會出現不一致的情況了嗎?確實,但是這樣做會嚴重降低系統的高可用型,你想想你的程序每秒能跑多少,你放到隊列中又能跑多少呢

二. 解決方案

目前,已知三種解決方案,讓我們來好好看看這其中的區別

  • 1. 先更新數據庫,再更新緩存
  • 2. 先刪除緩存,再更新數據庫
  • 3. 先更新數據庫,再刪除緩存

2.1 先更新數據庫,再更新緩存

首先,當我們一個改數據的請求進來,我們先更新數據庫的值,然後再把緩存中的值去更新,當我們下一次請求進來的時候保證數據是最新值

這其中有問題嗎?

2.1.1 問題1 每秒一萬次改請求,一次讀請求?

試想如上情況,每秒一萬次改請求,那我數據庫會一直更新,而且當我們數據庫更新的時候也會更新緩存值,那我其實只有一次讀請求,那我更新這一萬次緩存是不是沒意義? 而且會造成我們系統很大開銷,而緩存也沒被訪問幾次

在這裏插入圖片描述
那不如我們…

刪除緩存如何? 試想一下,要是我們直接每次更新數據庫的時候,不去更新緩存,而是直接刪除緩存,是不是情況就簡單很多,每秒一萬次請求,改數據庫然後刪除緩存,那我們的緩存並沒有持續更新,也不存在大開銷,並且下次我們查請求進來,也只不過多了一次生成緩存的操作

這點其實用了懶加載的思想,我們只有用到的時候採取生成,不用的時候就一直不用,是不是很好? 下面我們來討論這種方案

2.2 先刪除緩存,再更新數據庫

唉,既然上面這樣分析了,其實還是又出現了新的問題,那我們是先刪除緩存還是先更新數據庫呢?

在這裏插入圖片描述
我們設想有如下情景

  • (1)請求A進行寫操作,刪除緩存
  • (2)請求B查詢發現緩存不存在
  • (3)請求B去數據庫查詢得到舊值
  • (4)請求B將舊值寫入緩存
  • (5)請求A將新值寫入數據庫

如上,我們來分析一下,當請求A進行寫操作,剛剛刪除緩存還沒來得及更新數據庫,突然cpu任務調度停了,B請求查詢後沒緩存,就去數據庫查詢舊值,查到後緩存這個舊值,那我們這時A請求又進行了,去把數據庫的值更新了,這時候會出現什麼情況?緩存是舊值,但是數據庫是新值,出現數據不一致,而且每次有新請求進來,在緩存沒失效的時候,永遠查的都是舊值.

這樣會一直出現髒數據,那我們怎麼解決呢?

延時雙刪策略

例如在我們A請求進行寫操作的時候,第一次不是刪除了緩存嗎,但容易被B請求更新成髒數據,那我們就等一段時間(休眠一段時間)再進行刪除緩存,是不是可以把之前B請求的髒數據刪除掉,下次新請求進來查詢的時候查不到緩存,會去數據庫緩存最新值

但是,又拋出一些問題,如何確定這個睡眠時間是多少呢?根據自己的業務可以調整休眠時間,也有不確定性,其次要是你的數據庫是讀寫分離架構會出現如下問題

  • (1)請求A進行寫操作,刪除緩存
  • (2)請求A將數據寫入數據庫了,
  • (3)請求B查詢緩存發現,緩存沒有值
  • (4)請求B去從庫查詢,這時,還沒有完成主從同步,因此查詢到的是舊值
  • (5)請求B將舊值寫入緩存
  • (6)數據庫完成主從同步,從庫變爲新值

這樣也會出現數據不一致的情況,解決的策略還是延時雙刪策略,適當將休眠時間延長,在主從同步後進行雙刪,於是這樣加的時間值以及犧牲系統吞吐量來解決是不可取的,所以我們開另外一個線程進行異步刪除,不用再休眠系統

但是問題最終解決了嗎?

在這裏插入圖片描述
如果我們刪除失敗怎麼辦?

比如在上面延時雙刪的情況下,我們第二次刪除失敗,那前面一切不都是白做了?瓶頸口就在這裏,只要第二次刪除緩存失敗,一切都白給了~~

2.3 先更新數據庫,再刪除緩存

老外提出了一個緩存更新套路,名爲 Cache-Aside pattern 。

另外,知名社交網站facebook也在論文《Scaling Memcache at Facebook》中提出,他們用的也是先更新數據庫,再刪緩存的策略。

更新:先把數據存到數據庫中,成功後,再讓緩存失效。
在這裏插入圖片描述

首先,我們再來分析一下這種模式會出現什麼問題

  • (1)緩存剛好失效
  • (2)請求A查詢數據庫,得一箇舊值
  • (3)請求B將新值寫入數據庫
  • (4)請求B刪除緩存
  • (5)請求A將查到的舊值寫入緩存

也會出現緩存與數據庫不一致的情況,但其實這種情況出現的概率很低

首先,這種情況出現在讀請求穿插寫請求中,且寫請求用時比讀請求長,從上面情況來分析不難得出.其次數據庫寫操作時長是遠遠大於讀操作時長的,這也是爲什麼要數據庫讀寫分離的原因,所以出現上述條件是非常苛刻的,再來說說爲什麼先刪除緩存再寫數據庫這種情況的意外發生概率高,主要是因爲**寫操作中穿插讀操作,而讀操作時長非常短,**極易造成數據不一致

畢竟我們是優秀的程序員嘛,但其實如果要是真發生上述情況的話
在這裏插入圖片描述

開玩笑~ 那我們還是採用延時雙刪策略,保證讀請求後刪除緩存即可

2.4 最終問題

那我們就是會出現刪除緩存失敗的時候嘛,那你怎麼解決呢?

在這裏插入圖片描述

這裏借鑑孤獨煙大神的做法,只能怪自己太菜了唉~

方案一
在這裏插入圖片描述
流程如下所示
(1)更新數據庫數據;
(2)緩存因爲種種問題刪除失敗
(3)將需要刪除的key發送至消息隊列
(4)自己消費消息,獲得需要刪除的key
(5)繼續重試刪除操作,直到成功

然而,該方案有一個缺點,對業務線代碼造成大量的侵入。於是有了方案二,在方案二中,啓動一個訂閱程序去訂閱數據庫的binlog,獲得需要操作的數據。在應用程序中,另起一段程序,獲得這個訂閱程序傳來的信息,進行刪除緩存操作。

方案二
在這裏插入圖片描述
流程如下圖所示:
(1)更新數據庫數據
(2)數據庫會將操作信息寫入binlog日誌當中
(3)訂閱程序提取出所需要的數據以及key
(4)另起一段非業務代碼,獲得該信息
(5)嘗試刪除緩存操作,發現刪除失敗
(6)將這些信息發送至消息隊列
(7)重新從消息隊列中獲得該數據,重試操作。

借鑑大神操作 附上大神博客

製作不易,轉載都請標註 ~ 你看不懂,你也打不着我啊

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章