淺析數據庫與緩存的雙寫一致性問題


緩存由於其高並發和高性能的特性,在項目中被廣泛使用。讀緩存流程如下圖:

讀取緩存流程

雙寫一致性有以下三個要求:

  1. 緩存不能讀到臟數據
  2. 緩存可能會讀到過期數據,但要在可容忍時間內實現最終一致
  3. 這個可容忍時間盡可能的小

要想同時滿足上面三條,可以采用讀請求和寫請求串行化,串到一個內存隊列里去,這樣就可以保證一定不會出現不一致的情況。但是,串行化之后,就會導致系統的吞吐量會大幅度的降低,要用比正常情況下多幾倍的機器去支撐線上請求。

雙寫串行化

所以,在這里,我們討論三種常見方法:

  1. 先更新數據庫,再更新緩存
  2. 先刪除緩存,再更新數據庫
  3. 先更新數據庫,再刪除緩存

1. 先更新數據庫,再更新緩存

這種方法是大家普遍反對的,原因集中在下面兩點:

原因1:線程安全角度。
同時有請求A和請求B進行更新操作,那么會出現:

  1. 線程A更新了數據庫
  2. 線程B更新了數據庫
  3. 線程B更新了緩存
  4. 線程A更新了緩存
    這就出現請求A更新緩存應該比請求B更新緩存早才對,但是因為網絡等原因,B卻比A更早更新了緩存。這就導致了臟數據,因此不考慮。

"先更新緩存,再更新數據庫"這種方案同理,也是造成臟數據,所以不被考慮

原因2:業務場景角度。
有如下兩點:

  1. 如果你是一個寫數據庫場景比較多,而讀數據場景比較少的業務需求,采用這種方案就會導致數據壓根還沒讀到,緩存就被頻繁的更新,浪費性能。
  2. 如果你寫入數據庫的值,並不是直接寫入緩存的,而是要經過一系列復雜的計算再寫入緩存。那么,每次寫入數據庫后,都再次計算寫入緩存的值,無疑是浪費性能的。顯然,刪除緩存更為適合。

如果一定要更新緩存,可以考慮給緩存數據增加版本號

為什么要刪除緩存

2. 先刪除緩存,再更新數據庫

該方案同樣會導致不一致。同時有請求A和請求B進行更新操作,那么會出現:

  1. 請求A進行寫操作,刪除緩存
  2. 請求B查詢發現緩存不存在
  3. 請求B去數據庫查詢得到舊值
  4. 請求B將舊值寫入緩存
  5. 請求A將新值寫入數據庫上述情況就會導致不一致的情形出現。而且,如果不采用給緩存設置過期時間策略,該數據永遠都是臟數據。

先刪除后更新

解決方法:

  1. 先刪除緩存
  2. 再寫數據庫(這兩步和原來一樣)
  3. 休眠一定時間(例如1秒或200ms),再次刪除緩存。這么做,可以將緩存臟數據再次刪除。

然而這種解決方案由於要休眠線程還是很影響吞吐量的

3. 先更新數據庫,再刪除緩存

這種方案是很多工程采用的方案,我們來看下是否一定安全。
假設有兩個請求,一個請求A做查詢操作,一個請求B做更新操作,那么會有如下情形產生

  1. 緩存剛好失效
  2. 請求A查詢數據庫,得一個舊值
  3. 請求B將新值寫入數據庫
  4. 請求B刪除緩存
  5. 請求A將查到的舊值寫入緩存

這樣,臟數據就產生了,然而上面的情況是假設在數據庫寫請求比讀請求還要快。實際上,工程中數據庫的讀操作的速度遠快於寫操作的。
要么通過2PC或是Paxos協議保證一致性,要么就是想盡辦法降低並發時臟數據的概率,大概是因為2PC太慢,而Paxos又太復雜,綜合考慮,Facebook選擇了這個第三種方案。

如果刪除緩存失敗了怎么辦?

啟動一個訂閱程序去訂閱數據庫的binlog,獲得需要操作的數據。在應用程序中,另起一段程序,獲得這個訂閱程序傳來的信息,進行刪除緩存操作。

刪除緩存重試

阿里開源的中間件canal可以完成訂閱binlog日志的功能。

總結

本文是對目前互聯網中已有的一致性方案進行了一個總結,希望大家有所收獲。

最后,限於筆者經驗水平有限,歡迎讀者就文中的觀點提出寶貴的建議和意見。如果想獲得更多的學習資源或者想和更多的技術愛好者一起交流,可以關注我的公眾號『全菜工程師小輝』后台回復關鍵詞領取學習資料、進入前后端技術交流群和程序員副業群。同時也可以加入程序員副業群Q群:735764906 一起交流。

哎呀,如果我的名片丟了。微信搜索“全菜工程師小輝”,依然可以找到我


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM