勝芳網(wǎng)站建設(shè)qiansi全國疫情最新情況公布
【redis】redis緩存與數(shù)據(jù)庫的一致性
- 【1】四種同步策略
- 【2】更新緩存還是刪除緩存
- (1)更新緩存
- (2)刪除緩存
- 【3】先更新數(shù)據(jù)庫還是先刪除緩存
- (1)出現(xiàn)失敗時候的情況
- 1-先刪除緩存,再更新數(shù)據(jù)庫(更新數(shù)據(jù)庫失敗了)
- 2-先更新數(shù)據(jù)庫,再刪除緩存(刪除緩存失敗了)
- 3-總結(jié)
- (2)沒有出現(xiàn)失敗時候的情況
- 1-先刪除緩存,再更新數(shù)據(jù)庫
- 延時雙刪解決這個問題
- 如果是讀寫分離的架構(gòu)怎么辦(強制讀主庫)
- 刪除失敗了怎么辦?
- 2-先更新數(shù)據(jù)庫,再刪除緩存(最優(yōu)方案)
- 利用消息隊列進行刪除的補償
- 【4】總結(jié)
【1】四種同步策略
想要保證緩存與數(shù)據(jù)庫的雙寫一致,一共有4種方式,即4種同步策略:
(1)先更新緩存,再更新數(shù)據(jù)庫;
(2)先更新數(shù)據(jù)庫,再更新緩存;
(3)先刪除緩存,再更新數(shù)據(jù)庫;
(4)先更新數(shù)據(jù)庫,再刪除緩存。
更新緩存與刪除緩存哪種方式更合適?應(yīng)該先操作數(shù)據(jù)庫還是先操作緩存?
【2】更新緩存還是刪除緩存
(1)更新緩存
(1)優(yōu)點
每次數(shù)據(jù)變化都及時更新緩存,所以查詢時不容易出現(xiàn)未命中的情況。
(2)缺點
更新緩存的消耗比較大。如果數(shù)據(jù)需要經(jīng)過復(fù)雜的計算再寫入緩存,那么頻繁的更新緩存,就會影響服務(wù)器的性能。如果是寫入數(shù)據(jù)頻繁的業(yè)務(wù)場景,那么可能頻繁的更新緩存時,卻沒有業(yè)務(wù)讀取該數(shù)據(jù)。
(2)刪除緩存
(1)優(yōu)點
操作簡單,無論更新操作是否復(fù)雜,都是將緩存中的數(shù)據(jù)直接刪除。
(2)缺點
刪除緩存后,下一次查詢緩存會出現(xiàn)未命中,這時需要重新讀取一次數(shù)據(jù)庫。從上面的比較來看,一般情況下,刪除緩存是更優(yōu)的方案。
【3】先更新數(shù)據(jù)庫還是先刪除緩存
(1)出現(xiàn)失敗時候的情況
首先,我們將先刪除緩存與先更新數(shù)據(jù)庫,在出現(xiàn)失敗時進行一個對比:
1-先刪除緩存,再更新數(shù)據(jù)庫(更新數(shù)據(jù)庫失敗了)
先刪除緩存再更新數(shù)據(jù)庫,在出現(xiàn)失敗時可能出現(xiàn)的問題:
(1)線程A刪除緩存成功,線程A更新數(shù)據(jù)庫失敗;
(2)線程B從緩存中讀取數(shù)據(jù);由于緩存被刪,進程B無法從緩存中得到數(shù)據(jù),進而從數(shù)據(jù)庫讀取數(shù)據(jù);此時數(shù)據(jù)庫中的數(shù)據(jù)更新失敗,線程B從數(shù)據(jù)庫成功獲取舊的數(shù)據(jù),然后將數(shù)據(jù)更新到了緩存。
(3)最終,緩存和數(shù)據(jù)庫的數(shù)據(jù)是一致的,但仍然是舊的數(shù)據(jù)
2-先更新數(shù)據(jù)庫,再刪除緩存(刪除緩存失敗了)
先更新數(shù)據(jù)庫再刪除緩存,在出現(xiàn)失敗時可能出現(xiàn)的問題:
(1)線程A更新數(shù)據(jù)庫成功,線程A刪除緩存失敗;
(2)線程B讀取緩存成功,由于緩存刪除失敗,所以線程B讀取到的是緩存中舊的數(shù)據(jù)。
(3)最后線程A刪除緩存成功,有別的線程訪問緩存同樣的數(shù)據(jù),與數(shù)據(jù)庫中的數(shù)據(jù)是一樣。
(4)最終,緩存和數(shù)據(jù)庫的數(shù)據(jù)是一致的,但是會有一些線程讀到舊的數(shù)據(jù)。
3-總結(jié)
經(jīng)過上面的比較,我們發(fā)現(xiàn)在出現(xiàn)失敗的時候,是無法明確分辨出先刪緩存和先更新數(shù)據(jù)庫哪個方式更好,以為它們都存在問題。上述場景出現(xiàn)的問題,應(yīng)該如何解決呢?都建議采用重試機制解決。
(2)沒有出現(xiàn)失敗時候的情況
1-先刪除緩存,再更新數(shù)據(jù)庫
(1)線程A刪除緩存成功;
(2)線程B讀取緩存失敗;
(3)線程B讀取數(shù)據(jù)庫成功,得到舊的數(shù)據(jù);
(4)線程B將舊的數(shù)據(jù)成功地更新到了緩存;
(5)線程A將新的數(shù)據(jù)成功地更新到數(shù)據(jù)庫。
可見,進程A的兩步操作均成功,但由于存在并發(fā),在這兩步之間,進程B訪問了緩存。最終結(jié)果是,緩存中存儲了舊的數(shù)據(jù),而數(shù)據(jù)庫中存儲了新的數(shù)據(jù),二者數(shù)據(jù)不一致。
延時雙刪解決這個問題
如果是先刪緩存、再更新數(shù)據(jù)庫,在沒有出現(xiàn)失敗時可能會導(dǎo)致數(shù)據(jù)的不一致。如果在實際的應(yīng)用中,出于某些考慮我們需要選擇這種方式,可以采用延時雙刪的策略,延時雙刪的基本思路如下:
(1)刪除緩存;
(2)更新數(shù)據(jù)庫;
(3)sleep N毫秒;
(4)再次刪除緩存。
public void write(String key, Object data) {Redis.delKey(key);db.updateData(data);Thread.sleep(1000);Redis.delKey(key);
}
阻塞一段時間之后,再次刪除緩存,就可以把這個過程中緩存中不一致的數(shù)據(jù)刪除掉。而具體的時間,要評估你這項業(yè)務(wù)的大致時間,按照這個時間來設(shè)定即可。最終保證了數(shù)據(jù)庫和緩存的數(shù)據(jù)一致
如果是讀寫分離的架構(gòu)怎么辦(強制讀主庫)
如果數(shù)據(jù)庫采用的是讀寫分離的架構(gòu),那么又會出現(xiàn)新的問題,如下圖:
此時來了兩個請求,請求 A(更新操作) 和請求 B(查詢操作)
(1)請求 A 更新操作,刪除了 Redis;
(2)請求主庫進?更新操作,主庫與從庫進行同步數(shù)據(jù)的操作;
(3)請 B 查詢操作,發(fā)現(xiàn) Redis 中沒有數(shù)據(jù);
(4)去從庫中拿去數(shù)據(jù);
(5)此時主從同步數(shù)據(jù)還未完成,拿到的數(shù)據(jù)是舊數(shù)據(jù);
此時的解決辦法就是如果是對 Redis 進行填充數(shù)據(jù)的查詢數(shù)據(jù)庫操作,那么就強制將其指向主庫進?查詢。
刪除失敗了怎么辦?
如果刪除依然失敗,則可以增加重試的次數(shù),但是這個次數(shù)要有限制,當(dāng)超出一定的次數(shù)時,要采取報錯、記日志、發(fā)郵件提醒等措施。
2-先更新數(shù)據(jù)庫,再刪除緩存(最優(yōu)方案)
(1)線程A更新數(shù)據(jù)庫成功;
(2)線程B讀取緩存成功;
(3)線程A刪除緩存成功。
可見,最終緩存與數(shù)據(jù)庫的數(shù)據(jù)是一致的,并且都是最新的數(shù)據(jù)。但線程B在這個過程里讀到了舊的數(shù)據(jù),可能還有其他線程也像線程B一樣,在這兩步之間讀到了緩存中舊的數(shù)據(jù),但因為這兩步的執(zhí)行速度會比較快,所以影響不大。對于這兩步之后,其他進程再讀取緩存數(shù)據(jù)的時候,就不會出現(xiàn)類似于進程B的問題了。
利用消息隊列進行刪除的補償
先更新數(shù)據(jù)庫,后刪除緩存這?種情況也會出現(xiàn)問題,比如更新數(shù)據(jù)庫成功了,但是在刪除緩存的階段出錯了沒有刪除成功,那么此時再讀取緩存的時候每次都是錯誤的數(shù)據(jù)了。
此時解決方案就是利用消息隊列進行刪除的補償。具體的業(yè)務(wù)邏輯?語?描述如下:
(1)請求 線程A 先對數(shù)據(jù)庫進行更新操作;
(2)在對 Redis 進行刪除操作的時候發(fā)現(xiàn)報錯,刪除失敗;
(3)此時將Redis 的 key 作為消息體發(fā)送到消息隊列中;
(4)系統(tǒng)接收到消息隊列發(fā)送的消息后再次對 Redis 進行刪除操作;
但是這個方案會有?個缺點就是會對業(yè)務(wù)代碼造成大量的侵入,深深的耦合在?起,所以這時會有?個優(yōu)化的方法,我們知道對 Mysql 數(shù)據(jù)庫更新操作后再 binlog 日志中我們都能夠找到相應(yīng)的操作,那么我們可以訂閱 Mysql 數(shù)據(jù)庫的 binlog 日志對緩存進行操作。
【4】總結(jié)
一般情況下,刪除緩存是比更新緩存更優(yōu)的方案;先更新數(shù)據(jù)庫是比先刪除緩存更優(yōu)的方案;總的來說【先更新數(shù)據(jù)庫,再刪除緩存】就是四個策略中影響最小,效果最優(yōu)的方案。
但是如果需要使用【先刪除緩存,再更新數(shù)據(jù)庫】的方案的話,可以使用【延時雙刪】【讀寫分離時強制讀主庫】【重試機制】來解決問題。
如果使用【先更新數(shù)據(jù)庫,再刪除緩存】時出現(xiàn)刪除緩存失敗的情況,可以使用【binlog同步到redis】來解決問題。