我直接先拋一下結(jié)論:在滿足實(shí)時(shí)性的條件下,不存在兩者完全保存一致的方案,只有最終一致性方案。根據(jù)網(wǎng)上的眾多解決方案,總結(jié)出 6 種,直接看目錄:
不好的方案
1. 先寫 MySQL,再寫 Redis
圖解說(shuō)明:
這是一副時(shí)序圖,描述請(qǐng)求的先后調(diào)用順序;
橘黃色的線是請(qǐng)求 A,黑色的線是請(qǐng)求 B;
橘黃色的文字,是 MySQL 和 Redis 最終不一致的數(shù)據(jù);
數(shù)據(jù)是從 10 更新為 11;
后面所有的圖,都是這個(gè)含義,不再贅述。
請(qǐng)求 A、B 都是先寫 MySQL,然后再寫 Redis,在高并發(fā)情況下,如果請(qǐng)求 A 在寫 Redis 時(shí)卡了一會(huì),請(qǐng)求 B 已經(jīng)依次完成數(shù)據(jù)的更新,就會(huì)出現(xiàn)圖中的問題。
這個(gè)圖已經(jīng)畫的很清晰了,我就不用再去啰嗦了吧,不過這里有個(gè)前提,就是對(duì)于讀請(qǐng)求,先去讀 Redis,如果沒有,再去讀 DB,但是讀請(qǐng)求不會(huì)再回寫 Redis。大白話說(shuō)一下,就是讀請(qǐng)求不會(huì)更新 Redis。
2. 先寫 Redis,再寫 MySQL
同“先寫 MySQL,再寫 Redis”,看圖可秒懂。
3. 先刪除 Redis,再寫 MySQL
這幅圖和上面有些不一樣,前面的請(qǐng)求 A 和 B 都是更新請(qǐng)求,這里的請(qǐng)求 A 是更新請(qǐng)求,但是請(qǐng)求 B 是讀請(qǐng)求,且請(qǐng)求 B 的讀請(qǐng)求會(huì)回寫 Redis。
請(qǐng)求 A 先刪除緩存,可能因?yàn)榭D,數(shù)據(jù)一直沒有更新到 MySQL,導(dǎo)致兩者數(shù)據(jù)不一致。
這種情況出現(xiàn)的概率比較大,因?yàn)檎?qǐng)求 A 更新 MySQL 可能耗時(shí)會(huì)比較長(zhǎng),而請(qǐng)求 B 的前兩步都是查詢,會(huì)非常快。
好的方案
4. 先刪除 Redis,再寫 MySQL,再刪除 Redis
對(duì)于“先刪除 Redis,再寫 MySQL”,如果要解決最后的不一致問題,其實(shí)再對(duì) Redis 重新刪除即可,這個(gè)也是大家常說(shuō)的“緩存雙刪”。
為了便于大家看圖,對(duì)于藍(lán)色的文字,“刪除緩存 10”必須在“回寫緩存10”后面,那如何才能保證一定是在后面呢?網(wǎng)上給出的第一個(gè)方案是,讓請(qǐng)求 A 的最后一次刪除,等待 500ms。
對(duì)于這種方案,看看就行,反正我是不會(huì)用,太 Low 了,風(fēng)險(xiǎn)也不可控。
那有沒有更好的方案呢,我建議異步串行化刪除,即刪除請(qǐng)求入隊(duì)列
異步刪除對(duì)線上業(yè)務(wù)無(wú)影響,串行化處理保障并發(fā)情況下正確刪除。
如果雙刪失敗怎么辦,網(wǎng)上有給 Redis 加一個(gè)緩存過期時(shí)間的方案,這個(gè)不敢茍同。個(gè)人建議整個(gè)重試機(jī)制,可以借助消息隊(duì)列的重試機(jī)制,也可以自己整個(gè)表,記錄重試次數(shù),方法很多。
簡(jiǎn)單小結(jié)一下:
“緩存雙刪”不要用無(wú)腦的 sleep 500 ms;
通過消息隊(duì)列的異步&串行,實(shí)現(xiàn)最后一次緩存刪除;
緩存刪除失敗,增加重試機(jī)制。
5. 先寫 MySQL,再刪除 Redis
對(duì)于上面這種情況,對(duì)于第一次查詢,請(qǐng)求 B 查詢的數(shù)據(jù)是 10,但是 MySQL 的數(shù)據(jù)是 11,只存在這一次不一致的情況,對(duì)于不是強(qiáng)一致性要求的業(yè)務(wù),可以容忍。(那什么情況下不能容忍呢,比如秒殺業(yè)務(wù)、庫(kù)存服務(wù)等。)
當(dāng)請(qǐng)求 B 進(jìn)行第二次查詢時(shí),因?yàn)闆]有命中 Redis,會(huì)重新查一次 DB,然后再回寫到 Reids。
這里需要滿足 2 個(gè)條件:
緩存剛好自動(dòng)失效;
請(qǐng)求 B 從數(shù)據(jù)庫(kù)查出 10,回寫緩存的耗時(shí),比請(qǐng)求 A 寫數(shù)據(jù)庫(kù),并且刪除緩存的還長(zhǎng)。
對(duì)于第二個(gè)條件,我們都知道更新 DB 肯定比查詢耗時(shí)要長(zhǎng),所以出現(xiàn)這個(gè)情況的概率很小,同時(shí)滿足上述條件的情況更小。
6. 先寫 MySQL,通過 Binlog,異步更新 Redis
這種方案,主要是監(jiān)聽 MySQL 的 Binlog,然后通過異步的方式,將數(shù)據(jù)更新到 Redis,這種方案有個(gè)前提,查詢的請(qǐng)求,不會(huì)回寫 Redis。
這個(gè)方案,會(huì)保證 MySQL 和 Redis 的最終一致性,但是如果中途請(qǐng)求 B 需要查詢數(shù)據(jù),如果緩存無(wú)數(shù)據(jù),就直接查 DB;如果緩存有數(shù)據(jù),查詢的數(shù)據(jù)也會(huì)存在不一致的情況。
所以這個(gè)方案,是實(shí)現(xiàn)最終一致性的終極解決方案,但是不能保證實(shí)時(shí)性。
幾種方案比較
我們對(duì)比上面討論的 6 種方案:
先寫 Redis,再寫 MySQL
這種方案,我肯定不會(huì)用,萬(wàn)一 DB 掛了,你把數(shù)據(jù)寫到緩存,DB 無(wú)數(shù)據(jù),這個(gè)是災(zāi)難性的;
我之前也見同學(xué)這么用過,如果寫 DB 失敗,對(duì) Redis 進(jìn)行逆操作,那如果逆操作失敗呢,是不是還要搞個(gè)重試?
先寫 MySQL,再寫 Redis
對(duì)于并發(fā)量、一致性要求不高的項(xiàng)目,很多就是這么用的,我之前也經(jīng)常這么搞,但是不建議這么做;
當(dāng) Redis 瞬間不可用的情況,需要報(bào)警出來(lái),然后線下處理。
先刪除 Redis,再寫 MySQL
這種方式,我還真沒用過,直接忽略吧。
先刪除 Redis,再寫 MySQL,再刪除 Redis
這種方式雖然可行,但是感覺好復(fù)雜,還要搞個(gè)消息隊(duì)列去異步刪除 Redis。
先寫 MySQL,再刪除 Redis
比較推薦這種方式,刪除 Redis 如果失敗,可以再多重試幾次,否則報(bào)警出來(lái);
這個(gè)方案,是實(shí)時(shí)性中最好的方案,在一些高并發(fā)場(chǎng)景中,推薦這種。
先寫 MySQL,通過 Binlog,異步更新 Redis
對(duì)于異地容災(zāi)、數(shù)據(jù)匯總等,建議會(huì)用這種方式,比如 binlog + kafka,數(shù)據(jù)的一致性也可以達(dá)到秒級(jí);
純粹的高并發(fā)場(chǎng)景,不建議用這種方案,比如搶購(gòu)、秒殺等。
個(gè)人結(jié)論:
實(shí)時(shí)一致性方案:采用“先寫 MySQL,再刪除 Redis”的策略,這種情況雖然也會(huì)存在兩者不一致,但是需要滿足的條件有點(diǎn)苛刻,所以是滿足實(shí)時(shí)性條件下,能盡量滿足一致性的最優(yōu)解。
最終一致性方案:采用“先寫 MySQL,通過 Binlog,異步更新 Redis”,可以通過 Binlog,結(jié)合消息隊(duì)列異步更新 Redis,是最終一致性的最優(yōu)解。
審核編輯:湯梓紅
-
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
6808瀏覽量
88743 -
緩存
+關(guān)注
關(guān)注
1文章
229瀏覽量
26635 -
MySQL
+關(guān)注
關(guān)注
1文章
797瀏覽量
26399 -
Redis
+關(guān)注
關(guān)注
0文章
370瀏覽量
10830
原文標(biāo)題:幾種方案比較
文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論