Redis與數(shù)據(jù)庫數(shù)據(jù)一致性的原因及解決方案
一、概述
redis是一種開源、使用內(nèi)存存儲(chǔ)數(shù)據(jù)介質(zhì)的鍵值對存儲(chǔ)系統(tǒng)。redis的讀寫速度非常快,常用于應(yīng)用與數(shù)據(jù)庫之間做緩存層,能夠減少數(shù)據(jù)庫IO操作,提升數(shù)據(jù)庫性能,并提高應(yīng)用端的請求響應(yīng)速度。但涉及到并發(fā)讀寫數(shù)據(jù)時(shí)就容易出現(xiàn)redis與數(shù)據(jù)庫數(shù)據(jù)一致性的問題。
二、原因
應(yīng)用對數(shù)據(jù)庫的操作無外乎兩個(gè)操作讀操作和寫操作。 redis作為應(yīng)用與數(shù)據(jù)庫之間的緩存層,通常應(yīng)用在操作數(shù)據(jù)庫之前先操作redis,讀操作時(shí)只有redis不存在,才會(huì)操作數(shù)據(jù)庫,寫操作時(shí)需要更新數(shù)據(jù)庫和redis緩存。針對這兩個(gè)操作流程,我們需要分析下在什么場景下才會(huì)出現(xiàn)redis與數(shù)據(jù)庫數(shù)據(jù)不一致的問題。
1、讀取數(shù)據(jù)
讀取數(shù)據(jù)流程如下:
1. 應(yīng)用程序需要從數(shù)據(jù)庫讀取數(shù)據(jù)時(shí),先查詢r(jià)edis的緩存數(shù)據(jù)是否命中。
2. 若命中,直接返回。若未命中,再去查詢數(shù)據(jù)庫。
3. 將查詢到的數(shù)據(jù)先保存到redis中,并設(shè)置過期時(shí)間,再將數(shù)據(jù)返回到應(yīng)用。
以上是常用的一個(gè)讀取數(shù)據(jù)的場景,根據(jù)場景分析,只讀的情況下是不會(huì)出現(xiàn)redis與數(shù)據(jù)庫數(shù)據(jù)不一致的情況。
2、寫數(shù)據(jù)
寫數(shù)據(jù)流程一般操作流程可以分為以下4種:
1. 先更新緩存再更新數(shù)據(jù)庫。
2. 先刪除緩存再更新數(shù)據(jù)庫。
3. 先更新數(shù)據(jù)庫再更新緩存。
4. 先更新數(shù)據(jù)庫再刪除緩存。
根據(jù)以上4種流程分析,可以明確出兩個(gè)問題,一個(gè)是緩存是更新還是刪除,另外一個(gè)是先操作數(shù)據(jù)庫還是先操作緩存呢。
2.1、緩存是更新還是刪除
推薦使用刪除緩存。因?yàn)榫彺娴母鲁杀咎摺?/strong>由于大多數(shù)情況下數(shù)據(jù)并不是直接寫入緩存的,需要經(jīng)過一系列復(fù)雜的計(jì)算再寫入緩存的。若采用更新方式,那么每次寫入數(shù)據(jù)庫后,都需計(jì)算寫入緩存的值,無疑是浪費(fèi)性能的。刪除緩存操作簡單,副作用只是增加了一次cache miss,建議使用刪除策略。
2.2 先操作數(shù)據(jù)庫還是先操作緩存
2.2.1 先操作緩存
先操作緩存的流程如下:
先操作緩存的流程,就是先將緩存中數(shù)據(jù)刪除,再更新數(shù)據(jù)庫。
數(shù)據(jù)不一致
在讀寫并發(fā)操作的情況下,如何出現(xiàn)的數(shù)據(jù)不一致的問題呢。先看下并發(fā)流程:
1. 線程1發(fā)起修改數(shù)據(jù)請求,會(huì)進(jìn)行刪除緩存操作。
2. 接著更新數(shù)據(jù)庫時(shí)出現(xiàn)了網(wǎng)絡(luò)延遲。
3. 線程1由于網(wǎng)絡(luò)延遲還未對數(shù)據(jù)庫進(jìn)行修改,此時(shí)線程2執(zhí)行查詢請求,會(huì)去緩存中查詢數(shù)據(jù)。
4. 線程2在緩存中未查詢到數(shù)據(jù),再去查詢數(shù)據(jù)庫。
5. 線程2將查詢到數(shù)據(jù)舊數(shù)據(jù)放到緩存中,并將數(shù)據(jù)返回。
6. 線程1在線程2數(shù)據(jù)查詢完成后,才對數(shù)據(jù)庫進(jìn)行了修改。
在這個(gè)過程中就出現(xiàn)了redis與數(shù)據(jù)庫數(shù)據(jù)不一致的問題,只有等redis中數(shù)據(jù)過期時(shí)間到了,才能將新數(shù)據(jù)更新到緩存中。
2.2.2 先操作數(shù)據(jù)庫
先操作數(shù)據(jù)庫的流程如下:
先操作數(shù)據(jù)庫的流程,就是先對數(shù)據(jù)庫進(jìn)行修改,再將緩存中的數(shù)據(jù)進(jìn)行刪除。
數(shù)據(jù)不一致
在讀寫并發(fā)操作的情況下,如何出現(xiàn)的數(shù)據(jù)不一致的問題呢。
1. 線程1發(fā)起修改數(shù)據(jù)請求,先更新數(shù)據(jù)庫。
2. 線程2在線程1更新數(shù)據(jù)庫期間,發(fā)起查詢請求,從緩存中獲取到舊數(shù)據(jù)(臟數(shù)據(jù))。
3. 線程1完成數(shù)據(jù)庫更新后,刪除緩存中的數(shù)據(jù)。
在這個(gè)過程中出現(xiàn)了短暫的數(shù)據(jù)不一致,但redis和數(shù)據(jù)庫數(shù)據(jù)是最終一致性的。所以推薦先操作數(shù)據(jù)庫再操作緩存。
通過上述原因分析,可以得出在并發(fā)的讀寫情況下,正常使用redis與數(shù)據(jù)庫不管是先操作redis還是先操作數(shù)據(jù)庫,可能都會(huì)數(shù)據(jù)不一致問題。
注意:由于redis和數(shù)據(jù)庫操作不是原子的,若在redis和數(shù)據(jù)庫之間加鎖是可以實(shí)現(xiàn)數(shù)據(jù)一致,但也違背了使用redis的初衷。
二、解決方案
在不考慮redis操作失敗的情況下,保證redis與數(shù)據(jù)庫數(shù)據(jù)一致性的解決方案有4種。
1、延遲刪除機(jī)制
該機(jī)制是在數(shù)據(jù)庫數(shù)據(jù)更新后,先延遲一段時(shí)間后再次刪除緩存數(shù)據(jù)。線程1寫請求,線程2查詢請求,通過延遲雙刪機(jī)制保證redis與數(shù)據(jù)庫數(shù)據(jù)一致性。
通過(6)步延遲一段時(shí)間后再進(jìn)行redis的刪除,在并發(fā)讀寫情況下保證redis與數(shù)據(jù)庫數(shù)據(jù)一致性。具體延遲多長時(shí)間,需評估項(xiàng)目讀數(shù)據(jù)業(yè)務(wù)邏輯耗時(shí)(即線程2從數(shù)據(jù)庫讀取數(shù)據(jù)到更新緩存成功的時(shí)間)。確保查詢請求結(jié)束,更新請求可以刪除查詢請求造成的緩存臟數(shù)據(jù)。
2、binlog同步刪除機(jī)制
通過canal組件訂對binlog日志進(jìn)行訂閱,模仿數(shù)據(jù)庫的slave數(shù)據(jù)庫的備份請求,使得redis緩存數(shù)據(jù)刪除,保證redis與數(shù)據(jù)庫數(shù)據(jù)一致性。
通過上面兩種方式,在并發(fā)讀寫的情況下保證redis與數(shù)據(jù)庫數(shù)據(jù)最終一致性。但可能存在redis刪除失敗的情況,一旦出現(xiàn)就會(huì)有redis與數(shù)據(jù)庫數(shù)據(jù)不一致的問題。只有等redis中數(shù)據(jù)過期時(shí)間到了,才能將新數(shù)據(jù)更新到緩存中。
3、異步重試刪除機(jī)制
一旦緩存刪除失敗,可以通過重試機(jī)制設(shè)置重試次數(shù)保證一定刪除成功。如重試3次,三次操作都失敗則記錄日志并發(fā)送告警,通知技術(shù)人員進(jìn)行人工介入處理。在高并發(fā)環(huán)境下,重試最好使用異步方式,可以通過MQ實(shí)現(xiàn)這種機(jī)制。
通過(6)步延遲刪除緩存數(shù)據(jù)時(shí),刪除時(shí)失敗,緩存中存儲(chǔ)的還是臟數(shù)據(jù)(舊數(shù)據(jù))。線程1的應(yīng)用作為producer異步發(fā)送需要?jiǎng)h除key到MQ。線程1的應(yīng)用監(jiān)聽MQ,重試刪除操作。
通過重試刪除機(jī)制,可以能夠保證redis緩存一定能刪除成功,保證redis與數(shù)據(jù)庫數(shù)據(jù)一致性。但這種方式對業(yè)務(wù)代碼造成侵入,代碼過于耦合。
4、binlog解耦異步重試機(jī)制
可以使用阿里巴巴開源框架canal來實(shí)現(xiàn)程序解耦。通過利用canal提供的java客戶端,監(jiān)聽canal通知消息。當(dāng)java客戶端(項(xiàng)目)收到binlog變化的消息時(shí),完成對緩存的處理。
數(shù)據(jù)庫更新后,canal訂閱binlog日志,將變更的數(shù)據(jù)發(fā)送消息通知給java客戶端(spring boot項(xiàng)目)。java客戶端執(zhí)行延遲刪除緩存,若刪除失敗,java客戶端作為producer異步發(fā)送需要?jiǎng)h除key的MQ消息進(jìn)行重試。客戶端監(jiān)聽MQ消息,執(zhí)行重試刪除緩存操作。
三、總結(jié)
通過上述分析我們知道造成redis與數(shù)據(jù)庫數(shù)據(jù)不一致的問題主要在于并發(fā)情況下,讀寫并發(fā)操作可能會(huì)出現(xiàn)這個(gè)問題。通過4中解決方案,能夠很好的解決redis與數(shù)據(jù)庫數(shù)據(jù)一致性問題。
到此這篇關(guān)于Redis與數(shù)據(jù)庫數(shù)據(jù)一致性的原因及解決方案的文章就介紹到這了,更多相關(guān)Redis與數(shù)據(jù)庫數(shù)據(jù)一致性解決內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot/Springcloud項(xiàng)目集成redis進(jìn)行存取的過程解析
大家都知道Redis支持五種數(shù)據(jù)類型:string(字符串),hash(哈希),list(列表),set(集合),zset(sorted set:有序集合),本文重點(diǎn)給大家介紹Springboot/Springcloud項(xiàng)目集成redis進(jìn)行存取的過程,需要的朋友參考下吧2021-12-12Redis實(shí)現(xiàn)編碼生成規(guī)則方式
在自動(dòng)生成編碼時(shí)應(yīng)采用“MD+年月日+4位序列號(hào)”的規(guī)則,如“MD202310130001”,為避免使用隨機(jī)序列號(hào)導(dǎo)致的重復(fù)編碼,建議使用從0開始的自增序列號(hào),此外,使用Redis的incrBy功能實(shí)現(xiàn)序列號(hào)自增,可以有效提高效率和降低實(shí)現(xiàn)難度2023-01-01Unable?to?connect?to?Redis無法連接到Redis解決的全過程
這篇文章主要給大家介紹了關(guān)于Unable?to?connect?to?Redis無法連接到Redis解決的相關(guān)資料,文中通過圖文以及實(shí)例代碼將解決的過程介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03redisson中RRateLimiter分布式限流器的使用
Redisson Ratelimiter是Redisson框架中的一種限流算法,用于限制對資源的訪問頻率,本文主要介紹了redisson中RRateLimiter分布式限流器的使用,感興趣的可以了解一下2024-06-06Redis?鍵值對(key-value)數(shù)據(jù)庫實(shí)現(xiàn)方法
Redis 的鍵值對中的 key 就是字符串對象,而 value 可以是字符串對象,也可以是集合數(shù)據(jù)類型的對象,比如 List 對象,Hash 對象、Set 對象和 Zset 對象,這篇文章主要介紹了Redis?鍵值對數(shù)據(jù)庫是怎么實(shí)現(xiàn)的,需要的朋友可以參考下2024-05-05