如何保證MySQL和Redis的數(shù)據(jù)一致性?10張圖帶你搞定!

來源: 云加社區(qū)
作者:徐鑫
時(shí)間:2021-10-14
17798
本文的主要思路是首先帶大家認(rèn)識(shí)了解MySQL和Redis的數(shù)據(jù)一致性情況,然后進(jìn)行反推不一致的情況,從而進(jìn)行探究單線程中的不一致的情況。同時(shí)探究多線程中的不一致的情況,擬定數(shù)據(jù)一致性策略。

一、什么是數(shù)據(jù)的一致性

“數(shù)據(jù)一致”一般指的是:緩存中有數(shù)據(jù),緩存的數(shù)據(jù)值=數(shù)據(jù)庫中的值。但根據(jù)緩存中是有數(shù)據(jù)為依據(jù),則“一致”可以包含兩種情況:

緩存中有數(shù)據(jù),緩存的數(shù)據(jù)值=數(shù)據(jù)庫中的值

緩存中本沒有數(shù)據(jù),數(shù)據(jù)庫中的值=最新值(有請(qǐng)求查詢數(shù)據(jù)庫時(shí),會(huì)將數(shù)據(jù)寫入緩存,則變?yōu)樯厦娴摹耙恢隆睜顟B(tài))

“數(shù)據(jù)不一致”:緩存的數(shù)據(jù)值≠數(shù)據(jù)庫中的值;緩存或者數(shù)據(jù)庫中存在舊值,導(dǎo)致其他線程讀到舊數(shù)據(jù)。

二、數(shù)據(jù)不一致性情況及應(yīng)對(duì)策略

根據(jù)是否接收寫請(qǐng)求,可以把緩存分成讀寫緩存和只讀緩存。

只讀緩存:只在緩存進(jìn)行數(shù)據(jù)查找,即使用“更新數(shù)據(jù)庫+刪除緩存”策略。

讀寫緩存:需要在緩存中對(duì)數(shù)據(jù)進(jìn)行增刪改查,即使用“更新數(shù)據(jù)庫+更新緩存”策略。

(一)針對(duì)只讀緩存(更新數(shù)據(jù)庫+刪除緩存)

只讀緩存:新增數(shù)據(jù)時(shí),直接寫入數(shù)據(jù)庫;更新(修改/刪除)數(shù)據(jù)時(shí),先刪除緩存。后續(xù)訪問這些增刪改的數(shù)據(jù)時(shí),會(huì)發(fā)生緩存缺失,進(jìn)而查詢數(shù)據(jù)庫,更新緩存。

·新增數(shù)據(jù)時(shí),寫入數(shù)據(jù)庫;訪問數(shù)據(jù)時(shí),緩存缺失,查數(shù)據(jù)庫,更新緩存(始終是處于“數(shù)據(jù)一致”的狀態(tài),不會(huì)發(fā)生數(shù)據(jù)不一致性問題)

640.webp.jpg

·更新(修改/刪除)數(shù)據(jù)時(shí),會(huì)有個(gè)時(shí)序問題:更新數(shù)據(jù)庫與刪除緩存的順序(這個(gè)過程會(huì)發(fā)生數(shù)據(jù)不一致性問題)

在更新數(shù)據(jù)的過程中,可能會(huì)有如下問題:

·無并發(fā)請(qǐng)求下,其中一個(gè)操作失敗的情況。

·并發(fā)請(qǐng)求下,其他線程可能會(huì)讀到舊值

因此,要想達(dá)到數(shù)據(jù)一致性,需要保證兩點(diǎn):

·無并發(fā)請(qǐng)求下,保證A和B步驟都能成功執(zhí)行。

·并發(fā)請(qǐng)求下,在A和B步驟的間隔中,避免或消除其他線程的影響。

接下來,我們針對(duì)有/無并發(fā)場(chǎng)景,進(jìn)行分析并使用不同的策略。

·無并發(fā)情況

無并發(fā)請(qǐng)求下,在更新數(shù)據(jù)庫和刪除緩存值的過程中,因?yàn)椴僮鞅徊鸱殖蓛刹?,那么就很有可能存在“步驟1成功,步驟2失敗”的情況發(fā)生(由于單線程中步驟1和步驟2是串行執(zhí)行的,不太可能會(huì)發(fā)生“步驟2成功,步驟1失敗”的情況)。

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

640.webp (2).jpg

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

640.webp (3).jpg

640.webp (4).jpg

解決策略:

a.消息隊(duì)列+異步重試

無論使用哪一種執(zhí)行時(shí)序,可以在執(zhí)行步驟1時(shí),將步驟2的請(qǐng)求寫入消息隊(duì)列,當(dāng)步驟2失敗時(shí),就可以使用重試策略,對(duì)失敗操作進(jìn)行“補(bǔ)償”。

640.webp (5).jpg

具體步驟如下:

·把要?jiǎng)h除的緩存值或者是要更新的數(shù)據(jù)庫值暫存到消息隊(duì)列中(例如使用Kafka消息隊(duì)列)

·當(dāng)刪除緩存值或者是更新數(shù)據(jù)庫值成功時(shí),把這些值從消息隊(duì)列中去除,以免重復(fù)操作。

·當(dāng)刪除緩存值或者是更新數(shù)據(jù)庫值失敗時(shí),執(zhí)行失敗策略,重試服務(wù)從消息隊(duì)列中重新讀取這些值,然后再次進(jìn)行刪除或更新。

·刪除或者更新失敗時(shí),需要再次進(jìn)行重試,重試超過的一定次數(shù)。向業(yè)務(wù)層發(fā)送報(bào)錯(cuò)信息。

b.訂閱Binlog變更日志

·創(chuàng)建更新緩存服務(wù),接收數(shù)據(jù)變更的MQ消息,然后消費(fèi)消息,更新/刪除Redis中的緩存數(shù)據(jù)。

·使用Binlog實(shí)時(shí)更新/刪除Redis緩存。利用Canal,即將負(fù)責(zé)更新緩存的服務(wù)偽裝成一個(gè)MySQL的從節(jié)點(diǎn),從MySQL接收Binlog,解析Binlog之后,得到實(shí)時(shí)的數(shù)據(jù)變更信息,然后根據(jù)變更信息去更新/刪除Redis緩存。

·MQ+Canal策略,將Canal Server接收到的Binlog數(shù)據(jù)直接投遞到MQ進(jìn)行解耦,使用MQ異步消費(fèi)Binlog日志,以此進(jìn)行數(shù)據(jù)同步。

不管用MQ/Canal或者M(jìn)Q+Canal的策略來異步更新緩存,對(duì)整個(gè)更新服務(wù)的數(shù)據(jù)可靠性和實(shí)時(shí)性要求都比較高,如果產(chǎn)生數(shù)據(jù)丟失或者更新延時(shí)情況,會(huì)造成MySQL和Redis中的數(shù)據(jù)不一致。因此,使用這種策略時(shí),需要考慮出現(xiàn)不同步問題時(shí)的降級(jí)或補(bǔ)償方案。

·高并發(fā)情況

使用以上策略后,可以保證在單線程/無并發(fā)場(chǎng)景下的數(shù)據(jù)一致性。但是,在高并發(fā)場(chǎng)景下,由于數(shù)據(jù)庫層面的讀寫并發(fā),會(huì)引發(fā)的數(shù)據(jù)庫與緩存數(shù)據(jù)不一致的問題(本質(zhì)是后發(fā)生的讀請(qǐng)求先返回了)

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

假設(shè)線程A刪除緩存值后,由于網(wǎng)絡(luò)延遲等原因?qū)е挛醇案聰?shù)據(jù)庫,而此時(shí),線程B開始讀取數(shù)據(jù)時(shí)會(huì)發(fā)現(xiàn)緩存缺失,進(jìn)而去查詢數(shù)據(jù)庫。而當(dāng)線程B從數(shù)據(jù)庫讀取完數(shù)據(jù)、更新了緩存后,線程A才開始更新數(shù)據(jù)庫,此時(shí),會(huì)導(dǎo)致緩存中的數(shù)據(jù)是舊值,而數(shù)據(jù)庫中的是最新值,產(chǎn)生“數(shù)據(jù)不一致”。其本質(zhì)就是,本應(yīng)后發(fā)生的“B線程-讀請(qǐng)求”先于“A線程-寫請(qǐng)求”執(zhí)行并返回了。

640.webp (6).jpg

或者

640.webp (7).jpg

解決策略:

設(shè)置緩存過期時(shí)間+延時(shí)雙刪

通過設(shè)置緩存過期時(shí)間,若發(fā)生上述淘汰緩存失敗的情況,則在緩存過期后,讀請(qǐng)求仍然可以從DB中讀取最新數(shù)據(jù)并更新緩存,可減小數(shù)據(jù)不一致的影響范圍。雖然在一定時(shí)間范圍內(nèi)數(shù)據(jù)有差異,但可以保證數(shù)據(jù)的最終一致性。

此外,還可以通過延時(shí)雙刪進(jìn)行保障:在線程A更新完數(shù)據(jù)庫值以后,讓它先sleep一小段時(shí)間,確保線程B能夠先從數(shù)據(jù)庫讀取數(shù)據(jù),再把缺失的數(shù)據(jù)寫入緩存,然后,線程A再進(jìn)行刪除。后續(xù)其它線程讀取數(shù)據(jù)時(shí),發(fā)現(xiàn)緩存缺失,會(huì)從數(shù)據(jù)庫中讀取最新值。

redis.delKey(X)

db.update(X)

Thread.sleep(N)

redis.delKey(X)

sleep時(shí)間:在業(yè)務(wù)程序運(yùn)行的時(shí)候,統(tǒng)計(jì)下線程讀數(shù)據(jù)和寫緩存的操作時(shí)間,以此為基礎(chǔ)來進(jìn)行估算。

640.webp (8).jpg

注意:如果難以接受sleep這種寫法,可以使用延時(shí)隊(duì)列進(jìn)行替代。

先刪除緩存值再更新數(shù)據(jù)庫,有可能導(dǎo)致請(qǐng)求因緩存缺失而訪問數(shù)據(jù)庫,給數(shù)據(jù)庫帶來壓力,也就是緩存穿透的問題。針對(duì)緩存穿透問題,可以用緩存空結(jié)果、布隆過濾器進(jìn)行解決。

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

如果線程A更新了數(shù)據(jù)庫中的值,但還沒來得及刪除緩存值,線程B就開始讀取數(shù)據(jù)了,那么此時(shí),線程B查詢緩存時(shí),發(fā)現(xiàn)緩存命中,就會(huì)直接從緩存中讀取舊值。其本質(zhì)也是,本應(yīng)后發(fā)生的“B線程-讀請(qǐng)求”先于“A線程-刪除緩存”執(zhí)行并返回了。

640.webp (9).jpg

或者,在“先更新數(shù)據(jù)庫,再刪除緩存”方案下,“讀寫分離+主從庫延遲”也會(huì)導(dǎo)致不一致:

640.webp (10).jpg

解決方案:

a.延遲消息

憑借經(jīng)驗(yàn)發(fā)送「延遲消息」到隊(duì)列中,延遲刪除緩存,同時(shí)也要控制主從庫延遲,盡可能降低不一致發(fā)生的概率。

b.訂閱binlog,異步刪除

通過數(shù)據(jù)庫的binlog來異步淘汰key,利用工具(canal)將binlog日志采集發(fā)送到MQ中,然后通過ACK機(jī)制確認(rèn)處理刪除緩存。

c.刪除消息寫入數(shù)據(jù)庫

通過比對(duì)數(shù)據(jù)庫中的數(shù)據(jù),進(jìn)行刪除確認(rèn)先更新數(shù)據(jù)庫再刪除緩存,有可能導(dǎo)致請(qǐng)求因緩存缺失而訪問數(shù)據(jù)庫,給數(shù)據(jù)庫帶來壓力,也就是緩存穿透的問題。針對(duì)緩存穿透問題,可以用緩存空結(jié)果、布隆過濾器進(jìn)行解決。

d.加鎖

更新數(shù)據(jù)時(shí),加寫鎖;查詢數(shù)據(jù)時(shí),加讀鎖。

640.webp (11).jpg

建議:

優(yōu)先使用“先更新數(shù)據(jù)庫再刪除緩存”的執(zhí)行時(shí)序,原因主要有兩個(gè):

·先刪除緩存值再更新數(shù)據(jù)庫,有可能導(dǎo)致請(qǐng)求因緩存缺失而訪問數(shù)據(jù)庫,給數(shù)據(jù)庫帶來壓力。

·業(yè)務(wù)應(yīng)用中讀取數(shù)據(jù)庫和寫緩存的時(shí)間有時(shí)不好估算,進(jìn)而導(dǎo)致延遲雙刪中的sleep時(shí)間不好設(shè)置。

(二)針對(duì)讀寫緩存(更新數(shù)據(jù)庫+更新緩存)

讀寫緩存:增刪改在緩存中進(jìn)行,并采取相應(yīng)的回寫策略,同步數(shù)據(jù)到數(shù)據(jù)庫中。

同步直寫:使用事務(wù),保證緩存和數(shù)據(jù)更新的原子性,并進(jìn)行失敗重試(如果Redis本身出現(xiàn)故障,會(huì)降低服務(wù)的性能和可用性)

異步回寫:寫緩存時(shí)不同步寫數(shù)據(jù)庫,等到數(shù)據(jù)從緩存中淘汰時(shí),再寫回?cái)?shù)據(jù)庫(沒寫回?cái)?shù)據(jù)庫前,緩存發(fā)生故障,會(huì)造成數(shù)據(jù)丟失)

該策略在秒殺場(chǎng)中有見到過,業(yè)務(wù)層直接對(duì)緩存中的秒殺商品庫存信息進(jìn)行操作,一段時(shí)間后再回寫數(shù)據(jù)庫。

一致性:同步直寫>異步回寫,因此,對(duì)于讀寫緩存,要保持?jǐn)?shù)據(jù)強(qiáng)一致性的主要思路是:利用同步直寫,同步直寫也存在兩個(gè)操作的時(shí)序問題:更新數(shù)據(jù)庫和更新緩存。

·無并發(fā)情況

640.webp (12).jpg

·高并發(fā)情況

有四種場(chǎng)景會(huì)造成數(shù)據(jù)不一致:

640.webp (13).jpg

針對(duì)場(chǎng)景1和2的解決方案是:保存請(qǐng)求對(duì)緩存的讀取記錄,延時(shí)消息比較,發(fā)現(xiàn)不一致后,做業(yè)務(wù)補(bǔ)償針對(duì)場(chǎng)景3和4的解決方案是:對(duì)于寫請(qǐng)求,需要配合分布式鎖使用。寫請(qǐng)求進(jìn)來時(shí),針對(duì)同一個(gè)資源的修改操作,先加分布式鎖,保證同一時(shí)間只有一個(gè)線程去更新數(shù)據(jù)庫和緩存;沒有拿到鎖的線程把操作放入到隊(duì)列中,延時(shí)處理。用這種方式保證多個(gè)線程操作同一資源的順序性,以此保證一致性。

640.webp (14).jpg

其中,分布式鎖的實(shí)現(xiàn)可以使用以下策略:

640.webp (15).jpg

(三)強(qiáng)一致性策略

上述策略只能保證數(shù)據(jù)的最終一致性。要想做到強(qiáng)一致,最常見的方案是2PC、3PC、Paxos、Raft這類一致性協(xié)議,但它們的性能往往比較差,而且這些方案也比較復(fù)雜,還要考慮各種容錯(cuò)問題。如果業(yè)務(wù)層要求必須讀取數(shù)據(jù)的強(qiáng)一致性,可以采取以下策略:

·暫存并發(fā)讀請(qǐng)求

在更新數(shù)據(jù)庫時(shí),先在Redis緩存客戶端暫存并發(fā)讀請(qǐng)求,等數(shù)據(jù)庫更新完、緩存值刪除后,再讀取數(shù)據(jù),從而保證數(shù)據(jù)一致性。

·串行化

讀寫請(qǐng)求入隊(duì)列,工作線程從隊(duì)列中取任務(wù)來依次執(zhí)行

·修改服務(wù)Service連接池,id取模選取服務(wù)連接,能夠保證同一個(gè)數(shù)據(jù)的讀寫都落在同一個(gè)后端服務(wù)上。

·修改數(shù)據(jù)庫DB連接池,id取模選取DB連接,能夠保證同一個(gè)數(shù)據(jù)的讀寫在數(shù)據(jù)庫層面是串行的。

·使用Redis分布式讀寫鎖

將淘汰緩存與更新庫表放入同一把寫鎖中,與其它讀請(qǐng)求互斥,防止其間產(chǎn)生舊數(shù)據(jù)。讀寫互斥、寫寫互斥、讀讀共享,可滿足讀多寫少的場(chǎng)景數(shù)據(jù)一致,也保證了并發(fā)性。并根據(jù)邏輯平均運(yùn)行時(shí)間、響應(yīng)超時(shí)時(shí)間來確定過期時(shí)間。

public void write() {

    Lock writeLock = redis.getWriteLock(lockKey);

    writeLock.lock();

    try {

        redis.delete(key);

        db.update(record);

    } finally {

        writeLock.unlock();

    }

}


public void read() {

    if (caching) {

        return;

    }

    // no cache

    Lock readLock = redis.getReadLock(lockKey);

    readLock.lock();

    try {

        record = db.get();

    } finally {

        readLock.unlock();

    }

    redis.set(key, record);

}

(四)小結(jié)

640.webp (1).jpg

針對(duì)讀寫緩存時(shí):同步直寫,更新數(shù)據(jù)庫+更新緩存

640.webp (2).jpg

針對(duì)只讀緩存時(shí):更新數(shù)據(jù)庫+刪除緩存

640.webp (3).jpg

較為通用的一致性策略擬定:

在并發(fā)場(chǎng)景下,使用“更新數(shù)據(jù)庫+更新緩存”需要用分布式鎖保證緩存和數(shù)據(jù)一致性,且可能存在“緩存資源浪費(fèi)”和“機(jī)器性能浪費(fèi)”的情況;一般推薦使用“更新數(shù)據(jù)庫+刪除緩存”的方案。如果根據(jù)需要,熱點(diǎn)數(shù)據(jù)較多,可以使用“更新數(shù)據(jù)庫+更新緩存”策略。

在“更新數(shù)據(jù)庫+刪除緩存”的方案中,推薦使用推薦用“先更新數(shù)據(jù)庫,再刪除緩存”策略,因?yàn)橄葎h除緩存可能會(huì)導(dǎo)致大量請(qǐng)求落到數(shù)據(jù)庫,而且延遲雙刪的時(shí)間很難評(píng)估。

在“先更新數(shù)據(jù)庫,再刪除緩存”策略中,可以使用“消息隊(duì)列+重試機(jī)制”的方案保證緩存的刪除。并通過“訂閱binlog”進(jìn)行緩存比對(duì),加上一層保障。

此外,需要通過初始化緩存預(yù)熱、多數(shù)據(jù)源觸發(fā)、延遲消息比對(duì)等策略進(jìn)行輔助和補(bǔ)償?!径喾N數(shù)據(jù)更新觸發(fā)源:定時(shí)任務(wù)掃描,業(yè)務(wù)系統(tǒng)MQ、binlog變更MQ,相互之間作為互補(bǔ)來保證數(shù)據(jù)不會(huì)漏更新】

三、數(shù)據(jù)不一致性需注意其他問題

(一)k-v大小的合理設(shè)置

Redis key大小設(shè)計(jì):由于網(wǎng)絡(luò)的一次傳輸MTU最大為1500字節(jié),所以為了保證高效的性能,建議單個(gè)k-v大小不超過1KB,一次網(wǎng)絡(luò)傳輸就能完成,避免多次網(wǎng)絡(luò)交互;k-v是越小性能越好

Redis熱key:當(dāng)業(yè)務(wù)遇到單個(gè)讀熱key,通過增加副本來提高讀能力或是用hashtag把key存多份在多個(gè)分片中。

當(dāng)業(yè)務(wù)遇到單個(gè)寫熱key,需業(yè)務(wù)拆分這個(gè)key的功能,屬于設(shè)計(jì)不合理-當(dāng)業(yè)務(wù)遇到熱分片,即多個(gè)熱key在同一個(gè)分片上導(dǎo)致單分片cpu高,可通過hashtag方式打散。

(二)避免其他問題導(dǎo)致緩存服務(wù)器崩潰,進(jìn)而簡(jiǎn)直導(dǎo)致數(shù)據(jù)一致性策略失效緩存穿透、緩存擊穿、緩存雪崩、機(jī)器故障等問題

640.webp (4).jpg

(三)方案選定的思路

`確定緩存類型(讀寫/只讀)

`確定一致性級(jí)別

`確定同步/異步方式

`選定緩存流程

`補(bǔ)充細(xì)節(jié)

參考資料:

1.Redis與MySQL雙寫一致性如何保證

2.干貨|攜程最終一致和強(qiáng)一致性緩存實(shí)踐

3.大廠都是怎么做MySQL to Redis同步的

4.緩存與數(shù)據(jù)庫一致性策略

5.緩存與數(shù)據(jù)庫一致性保證

6.如何解決緩存和數(shù)據(jù)庫的數(shù)據(jù)不一致問題

7.Redis經(jīng)典問題,緩存(穿透,雪崩,擊穿,數(shù)據(jù)不一致,數(shù)據(jù)并發(fā)競(jìng)爭(zhēng),HotKey,BigKey),分布式鎖(watch樂觀鎖,setnx,Redisson)

8.Redisson分布式鎖場(chǎng)景和使用

立即登錄,閱讀全文
版權(quán)說明:
本文內(nèi)容來自于云加社區(qū),本站不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。文章內(nèi)容系作者個(gè)人觀點(diǎn),不代表快出海對(duì)觀點(diǎn)贊同或支持。如有侵權(quán),請(qǐng)聯(lián)系管理員(zzx@kchuhai.com)刪除!
相關(guān)文章
騰訊云數(shù)據(jù)庫PostgreSQL全面支持PG 17
騰訊云數(shù)據(jù)庫PostgreSQL全面支持PG 17
即日起,騰訊云PostgreSQL全面支持PostgreSQL 17.0。所有用戶可使用大版本升級(jí)能力升級(jí)至最新的PostgreSQL 17.0進(jìn)行體驗(yàn),也可以在產(chǎn)品購買頁直接購買。
騰訊云
云服務(wù)
2024-12-152024-12-15
高可用這個(gè)問題,加機(jī)器就能解決?
高可用這個(gè)問題,加機(jī)器就能解決?
互聯(lián)網(wǎng)服務(wù)的可用性問題是困擾企業(yè)IT人員的達(dá)摩克利斯之劍:防于未然,體現(xiàn)不出價(jià)值。已然發(fā)生,又面臨P0危機(jī)。就更別提穩(wěn)定性建設(shè)背后顯性的IT預(yù)算問題與隱性的人員成本問題。
騰訊云
云服務(wù)
2024-11-252024-11-25
TDSQL TDStore引擎版替換HBase:在歷史庫場(chǎng)景中的成本與性能優(yōu)勢(shì)
TDSQL TDStore引擎版替換HBase:在歷史庫場(chǎng)景中的成本與性能優(yōu)勢(shì)
HBase憑借其高可用性、高擴(kuò)展性和強(qiáng)一致性,以及在廉價(jià)PC服務(wù)器上的低部署成本,廣泛應(yīng)用于大規(guī)模數(shù)據(jù)分析。
騰訊云
云服務(wù)
2024-11-042024-11-04
復(fù)雜查詢性能弱,只讀分析引擎來幫忙
復(fù)雜查詢性能弱,只讀分析引擎來幫忙
隨著當(dāng)今業(yè)務(wù)的高速發(fā)展,復(fù)雜多表關(guān)聯(lián)的場(chǎng)景越來越普遍。但基于行式存儲(chǔ)的數(shù)據(jù)庫在進(jìn)行復(fù)雜查詢時(shí)性能相對(duì)較弱。
騰訊云
云服務(wù)
2024-11-022024-11-02
優(yōu)質(zhì)服務(wù)商推薦
更多
掃碼登錄
打開掃一掃, 關(guān)注公眾號(hào)后即可登錄/注冊(cè)
加載中
二維碼已失效 請(qǐng)重試
刷新
賬號(hào)登錄/注冊(cè)
小程序
快出海小程序
公眾號(hào)
快出海公眾號(hào)
商務(wù)合作
商務(wù)合作
投稿采訪
投稿采訪
出海管家
出海管家