如何保證Redis與數(shù)據(jù)庫的數(shù)據(jù)一致性
首先,分為兩種場景:
一. 針對讀場景:
(1) A請求查詢數(shù)據(jù),如果命中緩存,那么直接取緩存數(shù)據(jù)返回即可。如果請求中不存在,數(shù)據(jù)庫中存在,那么直接取數(shù)據(jù)庫數(shù)據(jù)返回,然后將數(shù)據(jù)同步到Redis中。不會存在數(shù)據(jù)不一致的情況。
(2) 在高并發(fā)的情況下,A請求和B請求一起訪問某條數(shù)據(jù),如果緩存中數(shù)據(jù)存在,直接返回即可,如果不存在,直接取數(shù)據(jù)庫數(shù)據(jù)返回即可。無論A請求B請求誰先誰后,本質(zhì)上沒有對數(shù)據(jù)進(jìn)行修改,數(shù)據(jù)本身沒變,只是從緩存中取還是從數(shù)據(jù)庫中取的問題,因此不會存在數(shù)據(jù)不一致的情況。
因此,單獨(dú)的讀場景是不會造成Redis與數(shù)據(jù)庫緩存不一致的情況,因此我們不用關(guān)心這種情況。
二. 針對寫場景:
(1) 如果該數(shù)據(jù)在緩存中不存在,那么直接修改數(shù)據(jù)庫中的數(shù)據(jù)即可,不會存在數(shù)據(jù)不一致的情況。
(2) 如果該數(shù)據(jù)在緩存中和數(shù)據(jù)庫中都存在,那么就需要既修改緩存中的數(shù)據(jù)又修改數(shù)據(jù)庫中的數(shù)據(jù),而且在高并發(fā)的場景下,還存在修后關(guān)系,這就會導(dǎo)致數(shù)據(jù)不一致的問題。
針對(2)的情況有兩個疑問:
(1)是刪除緩存數(shù)據(jù),等待下次查詢該數(shù)據(jù)時,緩存中沒有直接去數(shù)據(jù)庫中查詢,同時添加到緩存中,還是更新緩存呢?
(2)更新緩存中的數(shù)據(jù),是先更新緩存還是先更新數(shù)據(jù)庫呢?
關(guān)于疑問(1)有兩個方案
方案1:刪除緩存
優(yōu)點(diǎn):實(shí)現(xiàn)簡單,不需要再更新數(shù)據(jù)庫操作時在進(jìn)行更新數(shù)據(jù)邏輯,直接刪除對應(yīng)緩存的key即可。
缺點(diǎn):由于緩存被刪除,下次查詢無法命中緩存,需要在查詢后將數(shù)據(jù)寫入緩存,增加查詢邏輯。同時在高并發(fā)的情況下,同一時間大量請求訪問該條數(shù)據(jù),第一條查詢請求還未完成寫入緩存操作時,這種情況,大量查詢請求都會打到數(shù)據(jù)庫,加大數(shù)據(jù)庫壓力。
方案2:更新緩存
優(yōu)點(diǎn):緩存命中率高,只要緩存進(jìn)行了更新,后續(xù)的讀請求就不會出現(xiàn)緩存未命中的情況。
缺點(diǎn):在某些業(yè)務(wù)場景下,更新數(shù)據(jù)的成本較大,并不是單純將數(shù)據(jù)的數(shù)據(jù)查詢出來丟到緩存中即可,而是需要連接很多張表組裝對應(yīng)數(shù)據(jù)存入緩存中,并且可能存在更新后,該數(shù)據(jù)并不會被使用到的情況。
綜合分析
在一般的業(yè)務(wù)中一般都采用緩存淘汰這種方案,而非緩存更新。因為:
- 大多數(shù)情況下,redis緩存中的數(shù)據(jù)并不是完全復(fù)制數(shù)據(jù)庫中的數(shù)據(jù),而是將db中多張表的數(shù)據(jù)進(jìn)行了重新計算,篩選后更新到redis。如果在db某一張表的數(shù)據(jù)發(fā)生了變化的情況下,需要同步重新計算redis中值的話,更新成本過高。
- 緩存更新后的新值,無法保證一定會有讀請求命中,如果一直沒有請求命中該部分冷數(shù)據(jù),其實(shí)是產(chǎn)生了一定的資源浪費(fèi)(計算成本+存儲成本)。
- 相較于刪除緩存方案來說,僅有一次讀請求cache miss的結(jié)果來說,淘汰緩存策略的缺點(diǎn)完全可以容忍。
比如,A表中的字段,1分鐘更改了100次,如果采用更新緩存策略,則需要計算100次,哪怕1分鐘內(nèi)只有1次讀請求;如果采用淘汰緩存策略,如果1分鐘內(nèi)只有1次請求,則只需要計算1次即可,開銷大幅度降低。
關(guān)于疑問(2)有兩個方案
方案1:先更新緩存,后更新數(shù)據(jù)庫
正常情況
(1)A請求進(jìn)行寫操作,先淘汰緩存,再更新數(shù)據(jù)庫
(2)B請求進(jìn)行讀操作,由于A請求已將緩存淘汰,B請求沒有在redis中發(fā)現(xiàn)所需數(shù)據(jù),因此從數(shù)據(jù)庫中讀取數(shù)據(jù),并更新緩存到redis中
異常情況1
(1)A請求進(jìn)行寫操作,先淘汰緩存
(2)B請求進(jìn)行讀操作,由于A請求已將緩存淘汰,B請求沒有在redis中發(fā)現(xiàn)所需數(shù)據(jù),因此從數(shù)據(jù)庫中讀取數(shù)據(jù),并更新緩存到redis中。注意,此時redis中被更新的依然是老數(shù)據(jù),A請求的數(shù)據(jù)庫更新操作尚未完成
(3)A請求進(jìn)行數(shù)據(jù)庫更新操作。此時,數(shù)據(jù)庫中是新數(shù)據(jù),redis緩存中是老數(shù)據(jù),產(chǎn)生了數(shù)據(jù)不一致的問題。且該不一致會一直持續(xù)到緩存自然失效或者下次的更新操作
對于該種異常情況,提供兩種解決思路:
1.異步更新緩存
(1)A請求進(jìn)行寫操作,先淘汰緩存
(2)B請求進(jìn)行讀操作,由于A請求已將緩存淘汰,B請求沒有在redis中發(fā)現(xiàn)所需數(shù)據(jù),因此從數(shù)據(jù)庫中讀取數(shù)據(jù)。注意,此時不向redis寫入新的緩存策略
(3)A請求通過訂閱數(shù)據(jù)庫binlog,對redis緩存數(shù)據(jù)進(jìn)行異步更新
該方案雖然解決了數(shù)據(jù)不一致的問題,但是在數(shù)據(jù)庫更新操作完成前,所有的讀請求都會直接打到數(shù)據(jù)庫上,具有比較大的風(fēng)險。
2.延時雙刪
(1)A請求進(jìn)行寫操作,先淘汰緩存
(2)B請求進(jìn)行讀操作,由于A請求已將緩存淘汰,B請求沒有在redis中發(fā)現(xiàn)所需數(shù)據(jù),因此從數(shù)據(jù)庫中讀取數(shù)據(jù),并更新緩存到redis中。注意,此時redis中被更新的依然是老數(shù)據(jù),A請求的數(shù)據(jù)庫更新操作尚未完成。假設(shè)該步驟耗時N秒
(3)A請求進(jìn)行數(shù)據(jù)庫更新操作。
(4)由于此時redis中寫入了老數(shù)據(jù),因此A請求在休眠M(jìn)秒后(M略大于N),再次對redis進(jìn)行淘汰緩存操作
該方案雖然解決了數(shù)據(jù)不一致的問題,但是由于請求A在更新完數(shù)據(jù)庫之后,需要休眠M(jìn)秒再次淘汰緩存,一定程度上影響了數(shù)據(jù)更新操作的吞吐量。可以嘗試將等待M秒更新redis的操作放到另一個單獨(dú)的線程(比如消息隊列 + 重試機(jī)制)??梢杂行Ь徑馔掏铝拷档偷膯栴}。
異常情況2
(1)A請求進(jìn)行讀操作,此時redis緩存中沒有數(shù)據(jù),因此直接從數(shù)據(jù)庫中讀取數(shù)據(jù)
(2)B請求進(jìn)行寫操作,先淘汰緩存,再更新數(shù)據(jù)庫
(3)A請求進(jìn)行將從數(shù)據(jù)庫中讀到的老數(shù)據(jù),更新到redis。此時產(chǎn)生數(shù)據(jù)不一致問題。
該種異常情況發(fā)生概率極低,一般讀操作比寫操作要快。如有擔(dān)心,可以采用上述的延時刪除策略
方案2: 先更新數(shù)據(jù)庫,后更新緩存
正常情況
(1)A請求進(jìn)行寫操作,先更新數(shù)據(jù)庫,再淘汰緩存
(2)B請求進(jìn)行讀操作,由于A請求已將緩存淘汰,B請求沒有在redis中發(fā)現(xiàn)所需數(shù)據(jù),因此從數(shù)據(jù)庫中讀取數(shù)據(jù),并更新緩存到redis中
異常情況1
(1)A請求進(jìn)行寫操作,先更新數(shù)據(jù)庫
(2)B請求進(jìn)行讀操作,由于A請求尚未淘汰緩存,B請求在redis中發(fā)現(xiàn)所需數(shù)據(jù),因此直接返回老數(shù)據(jù),產(chǎn)生了數(shù)據(jù)不一致的問題
(3)A請求淘汰緩存。
(4)C請求進(jìn)行讀操作,發(fā)現(xiàn)redis中沒有數(shù)據(jù),因此從數(shù)據(jù)庫中讀取新數(shù)據(jù),并更新至緩存。數(shù)據(jù)不一致的問題解決。
該場景下,數(shù)據(jù)最終一致,只是在高并發(fā)下產(chǎn)生了一小段時間的數(shù)據(jù)不一致。
異常情況2
(1)A請求進(jìn)行讀操作,此時redis緩存中沒有數(shù)據(jù),因此直接從數(shù)據(jù)庫中讀取數(shù)據(jù)
(2)B請求進(jìn)行寫操作,更新數(shù)據(jù)庫,并將redis中緩存進(jìn)行了淘汰(雖然此時redis中并沒有任何的緩存)
(3)A請求將從數(shù)據(jù)庫中讀到的老數(shù)據(jù),更新到redis。此時產(chǎn)生數(shù)據(jù)不一致問題。
該種異常情況發(fā)生概率極低,一般讀操作比寫操作要快。如有擔(dān)心,可以采用上述的延時刪除策略。
總結(jié)
方案1:先淘汰緩存,后更新數(shù)據(jù)庫的策略,有可能導(dǎo)致長時間的數(shù)據(jù)不一致問題,可以通過延時雙刪 or 異步更新緩存策略進(jìn)行解決。
方案2:先更新數(shù)據(jù)庫,后更新緩存,有可能導(dǎo)致極短時間內(nèi)的數(shù)據(jù)不一致,但是數(shù)據(jù)最終是一致的。
以上就是如何保證Redis與數(shù)據(jù)庫的數(shù)據(jù)一致性的詳細(xì)內(nèi)容,更多關(guān)于Redis與數(shù)據(jù)庫 數(shù)據(jù)一致性的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis集群指定主從關(guān)系及動態(tài)增刪節(jié)點(diǎn)方式
這篇文章主要介紹了Redis集群指定主從關(guān)系及動態(tài)增刪節(jié)點(diǎn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01Windows安裝Redis并添加本地自啟動服務(wù)的實(shí)例詳解
這篇文章主要介紹了Windows安裝Redis并添加本地自啟動服務(wù)的實(shí)例詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11Satoken+Redis實(shí)現(xiàn)短信登錄、注冊、鑒權(quán)功能
這篇文章主要介紹了Satoken+Redis實(shí)現(xiàn)短信登錄、注冊、鑒權(quán)功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-01-01Redis客戶端連接遠(yuǎn)程Redis服務(wù)器方式
這篇文章主要介紹了Redis客戶端連接遠(yuǎn)程Redis服務(wù)器方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06Centos7 Redis主從搭建配置的實(shí)現(xiàn)
這篇文章主要介紹了Centos7 Redis主從搭建配置的實(shí)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06利用Redis實(shí)現(xiàn)訂單30分鐘自動取消
本文主要介紹了利用Redis實(shí)現(xiàn)訂單30分鐘自動取消,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06