網站首頁 編程語言 正文
目錄
1、四種同步策略:
想要保證緩存與數據庫的雙寫一致,一共有4種方式,即4種同步策略:
- 先更新緩存,再更新數據庫;
- 先更新數據庫,再更新緩存;
- 先刪除緩存,再更新數據庫;
- 先更新數據庫,再刪除緩存。
從這4種同步策略中,我們需要作出比較的是:
更新緩存與刪除緩存哪種方式更合適?應該先操作數據庫還是先操作緩存?
2、更新緩存還是刪除緩存
下面,我們來分析一下,應該采用更新緩存還是刪除緩存的方式。
2.1 更新緩存
優點:每次數據變化都及時更新緩存,所以查詢時不容易出現未命中的情況。
缺點:更新緩存的消耗比較大。如果數據需要經過復雜的計算再寫入緩存,那么頻繁的更新緩存,就會影響服務器的性能。如果是寫入數據頻繁的業務場景,那么可能頻繁的更新緩存時,卻沒有業務讀取該數據。
2.2 刪除緩存
優點:操作簡單,無論更新操作是否復雜,都是將緩存中的數據直接刪除。
缺點:刪除緩存后,下一次查詢緩存會出現未命中,這時需要重新讀取一次數據庫。從上面的比較來看,一般情況下,刪除緩存是更優的方案。
3、先操作數據庫還是緩存
下面,我們再來分析一下,應該先操作數據庫還是先操作緩存。
首先,我們將先刪除緩存與先更新數據庫,在出現失敗
時進行一個對比:
3.1 先刪除緩存再更新數據庫
如上圖,是先刪除緩存再更新數據庫,在出現失敗時可能出現的問題:
- 線程A刪除緩存成功,線程A更新數據庫失敗;
- 線程B從緩存中讀取數據;由于緩存被刪,進程B無法從緩存中得到數據,進而從數據庫讀取數據;此時數據庫中的數據更新失敗,線程B從數據庫成功獲取舊的數據,然后將數據更新到了緩存。
- 最終,緩存和數據庫的數據是一致的,但仍然是舊的數據
3.2 先更新數據庫再刪除緩存
如上圖,是先更新數據庫再刪除緩存,在出現失敗
時可能出現的問題:
- 線程A更新數據庫成功,線程A刪除緩存失敗;
- 線程B讀取緩存成功,由于緩存刪除失敗,所以線程B讀取到的是緩存中舊的數據。
- 最后線程A刪除緩存成功,有別的線程訪問緩存同樣的數據,與數據庫中的數據是一樣。
- 最終,緩存和數據庫的數據是一致的,但是會有一些線程讀到舊的數據。
經過上面的比較,我們發現在出現失敗
的時候,是無法明確分辨出先刪緩存和先更新數據庫哪個方式更好,以為它們都存在問題。后面我們會進一步對這兩種方式進行比較,但是在這里我們先探討一下,上述場景出現的問題,應該如何解決呢?
實際上,無論上面我們采用哪種方式去同步緩存與數據庫,在第二步出現失敗的時候,都建議采用重試機制解決,上面兩幅圖中已經畫了。
下面我們再將先刪緩存與先更新數據庫,在沒有出現失敗時
進行對比:
如上圖,是先刪除緩存再更新數據庫,在沒有出現失敗時
可能出現的問題:
- 線程A刪除緩存成功;
- 線程B讀取緩存失敗;
- 線程B讀取數據庫成功,得到舊的數據;
- 線程B將舊的數據成功地更新到了緩存;
- 線程A將新的數據成功地更新到數據庫。
可見,進程A的兩步操作均成功,但由于存在并發,在這兩步之間,進程B訪問了緩存。最終結果是,緩存中存儲了舊的數據,而數據庫中存儲了新的數據,二者數據不一致。
如上圖,是先更新數據庫再刪除緩存,在沒有出現失敗時
可能出現的問題:
- 線程A更新數據庫成功;
- 線程B讀取緩存成功;
- 線程A刪除緩存成功。
可見,最終緩存與數據庫的數據是一致的,并且都是最新的數據。但線程B在這個過程里讀到了舊的數據,可能還有其他線程也像線程B一樣,在這兩步之間讀到了緩存中舊的數據,但因為這兩步的執行速度會比較快,所以影響不大。對于這兩步之后,其他進程再讀取緩存數據的時候,就不會出現類似于進程B的問題了。
最終結論:
經過對比你會發現,先更新數據庫、再刪除緩存是影響更小的方案。如果第二步出現失敗的情況,則可以采用重試機制解決問題。
4、延時雙刪
上面我們提到,如果是先刪緩存、再更新數據庫,在沒有出現失敗時可能會導致數據的不一致。如果在實際的應用中,出于某些考慮我們需要選擇這種方式,那有辦法解決這個問題嗎?答案是有的,那就是采用延時雙刪的策略,延時雙刪的基本思路如下:
- 刪除緩存;
- 更新數據庫;
- sleep N毫秒;
- 再次刪除緩存。
public void write(String key, Object data) {
Redis.delKey(key);
db.updateData(data);
Thread.sleep(1000);
Redis.delKey(key);
}
阻塞一段時間之后,再次刪除緩存,就可以把這個過程中緩存中不一致的數據刪除掉。而具體的時間,要評估你這項業務的大致時間,按照這個時間來設定即可。
4.1 采用讀寫分離的架構怎么辦?
如果數據庫采用的是讀寫分離的架構,那么又會出現新的問題,如下圖:
此時來了兩個請求,請求 A(更新操作) 和請求 B(查詢操作)
- 請求 A 更新操作,刪除了 Redis;
- 請求主庫進?更新操作,主庫與從庫進行同步數據的操作;
- 請 B 查詢操作,發現 Redis 中沒有數據;
- 去從庫中拿去數據;
- 此時同步數據還未完成,拿到的數據是舊數據;
此時的解決辦法就是如果是對 Redis 進行填充數據的查詢數據庫操作,那么就強制將其指向主庫進?查詢。
刪除失敗了怎么辦?
如果刪除依然失敗,則可以增加重試的次數,但是這個次數要有限制,當超出一定的次數時,要采取報錯、記日志、發郵件提醒等措施。
5、利用消息隊列進行刪除的補償
先更新數據庫,后刪除緩存這?種情況也會出現問題,比如更新數據庫成功了,但是在刪除緩存的階段出錯了沒有刪除成功,那么此時再讀取緩存的時候每次都是錯誤的數據了。
此時解決方案就是利用消息隊列進行刪除的補償。具體的業務邏輯?語?描述如下:
- 請求 線程A 先對數據庫進行更新操作;
- 在對 Redis 進行刪除操作的時候發現報錯,刪除失敗;
- 此時將Redis 的 key 作為消息體發送到消息隊列中;
- 系統接收到消息隊列發送的消息后再次對 Redis 進行刪除操作;
但是這個方案會有?個缺點就是會對業務代碼造成大量的侵入,深深的耦合在?起,所以這時會有?個優化的方法,我們知道對 Mysql 數據庫更新操作后再 binlog 日志中我們都能夠找到相應的操作,那么我們可以訂閱 Mysql 數據庫的 binlog 日志對緩存進行操作。
原文鏈接:https://blog.csdn.net/weixin_44129618/article/details/122358133
相關推薦
- 2022-11-15 如何使用ASP.NET?Core?配置文件_實用技巧
- 2022-05-01 大數據Spark?Sql中日期轉換FROM_UNIXTIME和UNIX_TIMESTAMP的使用_數
- 2022-06-29 Qt?Design?Studio創建工程的實現方法_C 語言
- 2021-12-08 服務器并發量估算公式和計算方法_服務器其它
- 2022-04-22 golang數據類型【字符類型】以及fmt的輸出輸入、UTF-8 和 Unicode 有何區別?
- 2022-12-01 Rust實現AES加解密詳解_Rust語言
- 2022-11-25 Linux?apache實現https的配置方法_Linux
- 2022-10-30 Android?虛擬機中的內存分配與OOM問題詳解_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支