欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis中緩存和數(shù)據(jù)庫雙寫數(shù)據(jù)不一致的原因及解決方案

 更新時間:2024年03月14日 09:44:18   作者:coffee_babe  
這篇文章主要介紹了Redis中緩存和數(shù)據(jù)庫雙寫數(shù)據(jù)不一致的原因及解決方案,文中通過圖文結(jié)合的方式講解的非常詳細,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下

先更新數(shù)據(jù)庫,還是先更新緩存?

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

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

在這里插入圖片描述

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

舉個例子,比如【請求A】和【請求B】兩個請求,同時更新【同一條】數(shù)據(jù),則可能出現(xiàn)圖中的順序:

【請求A】先將數(shù)據(jù)庫的數(shù)據(jù)更新為1,然后在更新緩存前,【請求B】將數(shù)據(jù)庫的數(shù)據(jù)更新為2,緊接著把緩存更新為2,然后【請求A】更新緩存為1.此時,數(shù)據(jù)庫中的數(shù)據(jù)是2,而緩存中的數(shù)據(jù)卻是1,出現(xiàn)了緩存和數(shù)據(jù)庫中的數(shù)據(jù)不一致的現(xiàn)象

在這里插入圖片描述

2.先更新緩存,再更新數(shù)據(jù)庫。

舉個例子,【請求A】和【請求B】兩個請求,同時更新【同一條】數(shù)據(jù),則可能出現(xiàn)這樣的順序:

【請求A】先將緩存的數(shù)據(jù)更新為1,然后在更新數(shù)據(jù)庫前,【請求B】來了,將緩存的數(shù)據(jù)更新為2,緊接著把把數(shù)據(jù)庫更新為2,然后【請求A】將數(shù)據(jù)庫的數(shù)據(jù)更新為1.此時,數(shù)據(jù)庫中的數(shù)據(jù)是1,而緩存中的數(shù)據(jù)卻是2,出現(xiàn)了緩存和數(shù)據(jù)庫中的數(shù)據(jù)不一致的現(xiàn)象

在這里插入圖片描述

結(jié)論

所以,無論是【先更新數(shù)據(jù)庫,再更新緩存】,還是【先更新緩存,再更新數(shù)據(jù)庫】,這兩個方案都存在并發(fā)問題,當兩個請求并發(fā)更新同一條數(shù)據(jù)的時候,可能會出現(xiàn)緩存和數(shù)據(jù)庫中的數(shù)據(jù)不一致的現(xiàn)象

Cache Aside策略

Cache Aside(旁路緩存)策略,該策略可以細分為【讀策略】和【寫策略】
寫策略的步驟:

1.更新數(shù)據(jù)庫中的數(shù)據(jù);

2.刪除緩存中的數(shù)據(jù)

讀策略的步驟:

1.如果讀取的數(shù)據(jù)命中了緩存,則直接返回數(shù)據(jù)

2.如果讀取的數(shù)據(jù)沒有命中緩存,則從數(shù)據(jù)庫中讀取數(shù)據(jù),然后將數(shù)據(jù)寫入到緩存,并且返回給用戶

但是【寫策略】中的數(shù)據(jù)庫和緩存操作又有不同的順序:

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

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

在這里插入圖片描述

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

舉個例子,以用戶表的場景來分析。

假設(shè)某個用戶的年齡是20,請求A要更新用戶年齡為21,所以它會刪除緩存中的內(nèi)容。這時,另一個請求B要讀取這個用戶的年齡,它查詢緩存發(fā)現(xiàn)未命中后,會從數(shù)據(jù)庫中讀取到年齡為20,并且寫入到緩存中,然后請求A繼續(xù)更改數(shù)據(jù)庫,將用戶的年齡更新為21.

最終,該用戶年齡在緩存中是20(舊值),在數(shù)據(jù)庫中是21(新值),緩存和數(shù)據(jù)庫的數(shù)據(jù)不一致。可以看到,先刪除緩存,再更新數(shù)據(jù)庫,在【讀+寫】并發(fā)的時候,還是會出現(xiàn)緩存和數(shù)據(jù)庫的數(shù)據(jù)不一致的問題

在這里插入圖片描述

解決方案:

針對【先刪除緩存,再更新數(shù)據(jù)庫】方法在【讀+寫】并發(fā)請求而造成緩存不一致的解決辦法是【延遲雙刪】:
偽代碼示例。加了個睡眠時間,主要是為了確保請求A在睡眠的時候,請求B能夠在這一段時間內(nèi)完成【從數(shù)據(jù)庫讀取數(shù)據(jù),再把缺失的緩存寫入緩存】的操作,然后請求A睡眠完,再刪除緩存。所以請求A的睡眠時間就需要大于請求B【從數(shù)據(jù)庫讀取數(shù)據(jù)+寫入緩存】的時間。但是具體睡眠多久其實我們是沒法準確預(yù)估的,需要進行統(tǒng)計,所以這個方案盡可能保證一致性而已,極端情況下,依然也會出現(xiàn)緩存不一致的現(xiàn)象,因此,還是比較建議用【先更新數(shù)據(jù)庫,再刪除緩存】的方案

#刪除緩存
redis.delKey(X);
#更新數(shù)據(jù)庫
db.update(X);
#睡眠
Thread.sleep(N);
#再刪除緩存
redis.delKey(X);

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

繼續(xù)用【讀+寫】請求的并發(fā)的場景來分析。

假如某個用戶數(shù)據(jù)在緩存中不存在,請求A讀取讀取數(shù)據(jù)時從數(shù)據(jù)庫中查詢到年齡為20,在未寫入緩存中時另一個請求B更新數(shù)據(jù)。它更新數(shù)據(jù)庫中的年齡為21,并且清空緩存。這時請求A把數(shù)據(jù)庫中讀到的年齡為20的數(shù)據(jù)寫入到緩存中。最終,該用戶年齡在緩存中是20,數(shù)據(jù)庫中是21,緩存和數(shù)據(jù)庫數(shù)據(jù)不一致。

在這里插入圖片描述

分析

從上面的理論上分析,先更新數(shù)據(jù)庫,再刪除緩存也是會出現(xiàn)數(shù)據(jù)不一致性的問題,但是在實際中,這個問題出現(xiàn)的概率并不高。因為緩存的寫入通常要遠遠快于數(shù)據(jù)庫的寫入,所以在實際中很難出現(xiàn)請求B已經(jīng)更新了數(shù)據(jù)庫并且刪除了緩存,請求A才更新完緩存的情況。而一旦請求A早于請求B刪除緩存之前更新了緩存,那么接下來的請求就會因為緩存不命中而從數(shù)據(jù)庫中重新讀取數(shù)據(jù),所以不會出現(xiàn)這種不一致的情況。所以,【先更新數(shù)據(jù)庫+再刪除緩存】的方案,是可以保證數(shù)據(jù)一致性的,再加上一個【過期時間】,就算在這期間存在緩存數(shù)據(jù)不一直,有過期時間來兜底,這樣也能達到最終一致。

【先更新數(shù)據(jù)庫,再刪除緩存】存在的問題:

前面的分析都是建立再這兩個操作都能同時執(zhí)行成功的情況下,如果在刪除緩存(第二個操作)的時候失敗了,導(dǎo)致緩存中的數(shù)據(jù)是舊值,如果沒有前面的過期時間兜底的話,后續(xù)的請求就會一直是緩存中的就數(shù)據(jù)

【先更新數(shù)據(jù)庫,再刪除緩存】的方案雖然保證了數(shù)據(jù)庫與緩存的數(shù)據(jù)一致性,但是每次更新數(shù)據(jù)的時候,緩存的數(shù)據(jù)都會被刪除,這樣會對緩存的命中率帶來影響。所以,如果業(yè)務(wù)對緩存命中率有很高的要求,可以采用【更新數(shù)據(jù)庫+更新緩存】的方案,因為更新緩存并不會出現(xiàn)緩存未命中的情況,但是這個方案,前面提到,在兩個更新請求并發(fā)執(zhí)行的時候,會出現(xiàn)數(shù)據(jù)不一致的問題,因為更新數(shù)據(jù)庫和更新緩存這兩個操作是獨立的,我們又沒有對操作做任何并發(fā)控制,那么當兩個線程并發(fā)更新它們的話,就會因為寫入順序的不同造成數(shù)據(jù)不一致需要增加一些手段來解決這個問題,有兩種做法

  • 1.在更新緩存前先加個分布式鎖,保證同一時間之運行一個請求更新緩存,就不會產(chǎn)生并發(fā)問題了,但是引入鎖之后,對于寫入性能就會帶來影響
  • 2.在更新完緩存時,給緩存加上較短的過期時間,這樣即時出現(xiàn)緩存不一致的情況,緩存的數(shù)據(jù)也會很快過期,對業(yè)務(wù)來說也可以接受

如何保證【先更新數(shù)據(jù)庫,再刪除緩存】這兩個操作能執(zhí)行成功?

舉個例子:

應(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ù)不一致的問題,解決方案有兩種:

  • 1.重試機制
  • 2.訂閱MySQL binlog,再操作緩存

在這里插入圖片描述

1.重試機制。

我們可以引入消息隊列,將第二個操作(刪除緩存)要操作的數(shù)據(jù)加入到消息隊列,由消費者來操作數(shù)據(jù)。

  • 1.1 如果應(yīng)用刪除緩存失敗,可以從消息隊列中重新讀取數(shù)據(jù),然后再次刪除緩存,這個就是重試機制。當然,如果重試超過一定的次數(shù),還是沒有成功,就需要向業(yè)務(wù)層發(fā)送報錯消息了
  • 1.2 如果刪除緩存成功,就要把數(shù)據(jù)從消息隊列中移除,避免重復(fù)操作,否則就繼續(xù)重試

在這里插入圖片描述

2.訂閱MySQL binlog,再操作緩存

【先更新數(shù)據(jù)庫,再刪除緩存】的策略第一步是更新數(shù)據(jù)庫,那么更新數(shù)據(jù)庫成功,就會產(chǎn)生一條變更日志,記錄在binlog里。于是我們就可以通過訂閱binlog日志,拿到具體要操作的數(shù)據(jù),然后再執(zhí)行緩存刪除,阿里開源的Cannal中間件就是基于這個實現(xiàn)的。

Cannal模擬MySQL主從復(fù)制的交互協(xié)議,把自己偽裝成一個MySQL的從節(jié)點,向MySQL主節(jié)點發(fā)送dump請求,MySQL收到請求后,就會開始推送binlog給Cannal,Cannal解析binlog字節(jié)流之后,轉(zhuǎn)換為便于讀取的結(jié)構(gòu)化數(shù)據(jù),供下游程序訂閱使用.

在這里插入圖片描述

所以如果要想保證【先更新數(shù)據(jù)庫,再刪除緩存】策略第二個操作能執(zhí)行成功,我們可以使用【消息隊列來重試緩存的刪除】,或者【訂閱MySQL binlog再操作緩存】,這兩種方法有一個共同的特點,都是采用異步操作緩存

疑問

為什么是刪除緩存,而不是更新緩存?

刪除一個數(shù)據(jù),相比更新一個數(shù)據(jù)更加輕量級,出問題的概率更小。在實際業(yè)務(wù)中,緩存的數(shù)據(jù)可能不是直接來自數(shù)據(jù)庫表,也許來自多張底層數(shù)據(jù)表的聚合。比如商品詳情信息,在底層可能會關(guān)聯(lián)商品表、價格表、庫存表等,如果更新了一個價格字段,那么就要更新整個數(shù)據(jù)庫,還要關(guān)聯(lián)的去查詢和匯總各個周邊業(yè)務(wù)系統(tǒng)的數(shù)據(jù),這個操作會非常耗時。從另外一個角度,不是所有的緩存數(shù)據(jù)都是頻繁訪問的,更新后的緩存可能會長事件不被訪問,所以說,從計算資源和整體性能的考慮,更新的時候刪除緩存,等到下次查詢命中再填充緩存,是一個更好的方案。

系統(tǒng)設(shè)計中有一個設(shè)計叫Lazy Loading,適用于那些加載代價大的操作,刪除緩存而不是更新緩存,就是懶加載思想的一個應(yīng)用

以上就是Redis中緩存和數(shù)據(jù)庫雙寫數(shù)據(jù)不一致的原因及解決方案的詳細內(nèi)容,更多關(guān)于Redis緩存和數(shù)據(jù)庫雙寫不一致的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • redis學(xué)習(xí)之RDB、AOF與復(fù)制時對過期鍵的處理教程

    redis學(xué)習(xí)之RDB、AOF與復(fù)制時對過期鍵的處理教程

    這篇文章主要給大家介紹了關(guān)于redis學(xué)習(xí)之RDB、AOF與復(fù)制時對過期鍵處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用redis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • springmvc集成使用redis過程

    springmvc集成使用redis過程

    這篇文章主要介紹了springmvc集成使用redis過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Redis序列化存儲及日期格式的問題處理

    Redis序列化存儲及日期格式的問題處理

    這篇文章主要介紹了Redis序列化存儲及其日期格式的問題處理方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 基于Redis+Lua腳本實現(xiàn)分布式限流組件封裝的方法

    基于Redis+Lua腳本實現(xiàn)分布式限流組件封裝的方法

    這篇文章主要介紹了基于Redis+Lua腳本實現(xiàn)分布式限流組件封裝,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • Redis中List類型的常用命令

    Redis中List類型的常用命令

    本文主要介紹了Redis中List類型的常用命令,包含12種常用命令,具有一定的參考價值,感興趣的可以了解一下
    2024-06-06
  • Redis讀寫分離搭建的完整步驟

    Redis讀寫分離搭建的完整步驟

    為滿足讀多寫少的業(yè)務(wù)場景.最大化節(jié)約用戶成本.云數(shù)據(jù)庫Redis版推出了讀寫分離規(guī)格,為用戶提供透明、高可用、高性能、高靈活的讀寫分離服務(wù),這篇文章主要給大家介紹了關(guān)于Redis讀寫分離搭建的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • Redis key命令key的儲存方式

    Redis key命令key的儲存方式

    這篇文章主要介紹了Redis key命令key的儲存方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • Redis字符串原理的深入理解

    Redis字符串原理的深入理解

    這篇文章主要給大家介紹了關(guān)于Redis字符串原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • 詳解Redis如何多規(guī)則限流和防重復(fù)提交

    詳解Redis如何多規(guī)則限流和防重復(fù)提交

    市面上很多介紹redis如何實現(xiàn)限流的,但是大部分都有一個缺點,就是只能實現(xiàn)單一的限流,但是如果想一個接口兩種規(guī)則都需要滿足呢,使用本文就來介紹一下redis實現(xiàn)分布式多規(guī)則限流的方式吧
    2023-12-12
  • Redis緩存更新策略詳解

    Redis緩存更新策略詳解

    這篇文章主要為大家詳細介紹了Redis緩存更新策略,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07

最新評論