寫在最前面
在大型互聯網應用當中如果你的應用引入了緩存機制果港,那么有一個大前提就是你的業(yè)務場景上必須得接受數據的新鮮度上有可能會有一定時間的延遲。刪除緩存失敗是一個極小概率事件,且在不能保證所有操作100%成功的幾率下征峦,采用JOB補償的機制是目前比較成熟的解決方案于颖。
大并發(fā)量寫請求的應用呆贿,不可能去實時寫DB,基本都采用隊列+消息異步寫DB的機制森渐,不然會有大量的并發(fā)問題
緩存機制介紹
如今利用緩存機制來提高查詢效率已被廣泛用在各大生產環(huán)境做入,查詢數據的一般流程如下所示
- 如果數據在緩存里邊有,則直接從緩存取數據返回同衣。
- 如果緩存中沒有想要的數據竟块,則先去查詢數據庫,然后將數據庫查出來的數據寫到緩存中再返回
沒有更新數據的情況下耐齐,數據庫和緩存的數據是保持一致的浪秘;但是當要執(zhí)行數據的更新操作的時候,數據庫和緩存的數據就會出現不一致的情況
因此埠况,為了解決數據不一致的問題耸携,需要在更新數據庫的時候,對緩存做一些額外的操作询枚,有以下幾種方案
- 先刪緩存违帆,再更新數據庫
- 先更新數據庫,再刪緩存
- 緩存延時雙刪金蜀,更新前先刪除緩存刷后,然后更新數據,再延時刪除緩存
- 監(jiān)聽MySQL binlog進行緩存更新
之所以緩存不采取更新操作而是直接刪除是因為:高并發(fā)環(huán)境下渊抄,無論是先操作數據庫還是后操作數據庫而言尝胆,如果加上更新緩存,那就更加容易導致數據庫與緩存數據不一致問題护桦。(刪除緩存直接且簡單很多)
先刪除緩存含衔,再更新數據庫
該方案在線程A進行數據更新操作,線程B進行查詢操作時,有可能出現下面的情況導致數據不一致:
- 線程A刪除緩存
- 線程B查詢數據贪染,發(fā)現緩存數據不存在
- 線程B查詢數據庫缓呛,得到舊值,寫入緩存
- 線程A將新值更新到數據庫
這樣一來杭隙,緩存中的數據仍然是舊值
如果線程B執(zhí)行的是更新操作哟绊,線程B查詢得到的是舊值,A更新到數據庫新值痰憎,然后B基于舊值計算寫入了計算后的值票髓,A的更新操作被抹去了,這種情況下屬于更新數據事務原子性問題铣耘,需要用分布式鎖來解決洽沟。
先更新數據庫,再刪除緩存
當緩存失效時蜗细,線程B原子性被破壞時會出現不一致問題:
- 緩存失效了
- 線程B從數據庫讀取舊值
- 線程A從數據庫讀取舊值
- 線程B將新值更新到數據庫
- 線程B刪除緩存
- 線程A將舊值寫入緩存
這種情況概率很低裆操,實際上數據庫的寫操作會比讀操作慢得多,讀操作必需在寫操作前進入數據庫操作鳄乏,而又要晚于寫操作更新緩存跷车,這種情況下只需要線程B延時刪除緩存就好。另外在數據庫主從同步的情況下橱野,延時刪除還能防止數據更新還未從主數據庫同步到從數據庫的情況朽缴。
延時雙刪
延時雙刪即先刪除緩存,然后更新數據水援,再延時n ms后刪除緩存密强,這個我認為作用和更新數據庫再刪除緩存的策略幾乎是等同的(歡迎討論)
之所以設計為延時雙刪的目的在于當最后一次延時刪除緩存失敗的情況發(fā)生,至少一致性策略只會退化成先刪緩存再更新數據的策略蜗元。
刪除緩存失敗這種事情個人認為在生產環(huán)境緩存高可用的情況下幾乎不會出現或渤,且這種情況如果發(fā)生了,不如考慮一下重試機制奕扣。
異步更新緩存(基于訂閱binlog的同步機制)
通過異步更新緩存將緩存與數據庫的一致性同步從業(yè)務中獨立出來統一處理薪鹦,保證數據一致性
整體技術思路:
- 讀Redis:熱數據基本都在Redis
- 寫MySQL:增刪改都是操作MySQL
- 更新Redis數據:訂閱MySQ的數據操作記錄binlog,來更新到Redis
數據操作分為兩大部分:
- 全量更新(將全部數據一次性寫入redis)
- 增量更新(實時更新)
這樣一旦MySQL中產生了新的寫入、更新、刪除等操作汇恤,就可以把binlog相關的消息通過消息隊列推送至Redis月帝,Redis再根據binlog中的記錄灾常,對Redis進行更新。
這種同步機制類似于MySQL的主從備份機制,可以結合使用阿里的canal對MySQL的binlog進行訂閱。
總結
綜上所述端考,異步更新緩存雅潭、更新后延遲刪與延遲雙刪都是不錯的一致性解決方案,但除了異步更新緩存却特,其余兩個方案都會對業(yè)務線的代碼有侵入性扶供。