技能篇:關於緩存數據的一致性探討

為了更快響應請求,減少不必要的查詢,加速數據的處理,數據緩存是我們日常開發繞不過去的環節

image.png

關注公眾號,一起交流,微信搜一搜: 潛行前行

緩存的意義

數據的保存,離不開磁盤或者內存的操作。為了永久性的保存,數據最終還是會同步到磁盤上,小流量小並發的系統,直接使用 mysql 進行數據的操作即可滿足需求。但面對高並發大流量時,又應該怎麼去更新保存讀取數據呢?使用內存作為緩衝區,即緩存。CPU 操作內存空間的速度是比磁盤快一個大級別的,內存操作就是在公路上開汽車,磁盤讀寫則在小道上騎單車

  • 基於內存去做緩存,有兩種方案:一是基於本地內存實現,如簡單使用 HashMap,guava 的 LoadCache,Caffeine;二是依賴局域網中的其他中間件,如 redis,Memcache,SQLite。這些內存數據庫廣泛地被當做分佈式緩存中間件使用
  • 把數據攔截在內存上去操作,確實是提高了系統的性能。但是內存上的數據容易丟失,萬一停電,機器宕機,數據全就沒了,而且內存的硬件貴,容量小,並不是一個保存數據永久之地。因為我們最終還是要把數據同步到磁盤DB上,而同步就會出現一致性的問題

緩存的不一致性

  • 讀一致性:先讀取緩存數據,有數據則直接返回;如果讀取不到,則讀取BD上的數據,然後給緩存的數據設置過期時間,避免數據永久停留在內存上。讀一致性問題不大
  • 寫一致性:這裡的寫是專指新增的操作。和讀操作差不多,直接寫入數據庫即可,如果後續有讀操作,則使用讀一致性的操作步驟即可保證一致性
  • 更新一致性:更新操作則有點麻煩,有兩個問題:一是先更新緩存呢,還是刪除緩存?二是先操作緩存還是 DB ? 兩個問題組合起來就有四種方案了,各位帶着問題往下看

先更新緩存後更新BD

這個方案基本不可能會被採用的,因為出了問題是不可挽回的。想一想在多線程操作的情況下
image.png

為啥說這個方案不會被採用呢?設想用戶下單的場景。第一步:更新緩存里下單狀態為成功(假設此時會預定庫存);第二步讀取到下單成功狀態,然後準備去支付(此時第三步更新DB失敗了)

  • 假設支付階段,讀取到緩存里的下單狀態為成功,最終支付完成。因為下單階段會預定庫存,但實際 DB 更新失敗,這將導致超賣。收了錢卻沒貨發,等着被起訴吧,或者賠償用戶
  • 假設支付階段,剛好緩存失效,讀取到 DB 里的真實下單狀態,支付失敗。給用戶的感覺就是垃圾產品

先更新DB後更新緩存

先更新數據庫後更新緩存同樣會存在數據不一致性,請看以下場景
image.png
網絡問題可能會導致線程B更新緩存比線程A更快,而在第四步完成之後,緩存失效之前,緩存和數據庫就會存在不一致性問題

  • 將更新DB和更新緩存操作封裝在同一個事務?雖然可以做到強一致性,但這導致數據庫事務的延長,會導致服務性能下降,本來引入緩存是為加大性能,怎麼反向操作了
  • 可能你會認為不一致只是短暫的,可以接受,數據最終還是會達成一致性的。但是下面還有更好的方案為啥要選這個呢?
  • 還有如果寫操作多,讀操作少,這種方案就會導致,數據壓根還沒讀到,緩存就被頻繁的更新,浪費系統資源
  • 還有些場景,數據是要經過複雜的計算才寫入緩存的,而並非寫入數據庫的那個數據。在讀少寫多的業務,這多出的計算操作也是浪費系統資源的

先刪緩存後更新DB

看一下先刪緩存後更新DB方案
image.png

  • 這個方案比更新DB後更新緩存好的地方在於,不用事先計算緩存,更新緩存
  • 但在第四步操作後,先刪緩存後更新DB一樣存在短暫的不一致性,怎麼辦?可以採用 延遲雙刪方案

延遲雙刪方案

image.png
可以看到延遲雙刪方案增加了一步驟,在更新完數據之後,延遲一段時間再刪除緩存。至於這幾毫秒怎麼確定,則需要同學們自己根據相應服務數據的讀操作耗時時間確定。延遲刪除時間 = 讀操作耗時時間 + 浮動時間(大概幾十毫秒就行)

  • 延遲雙刪方案不能完美做到杜絕 緩存和DB不一致現象發生,只是極大概率減少數據不一致

先更新DB後刪緩存

image.png
該方案也是不能完美解決不了數據的不一致性,但同樣可以延遲刪除的策略來降低數據不一致性的發生概率

  • 對比先刪緩存後更新DB方案,優化的點在於少了一次刪除操作

延遲刪除

image.png

刪除失敗,怎麼補救

延遲刪除,會不會存在刪除失敗的情況呢,這時怎麼辦。可以採用下面兩種策略來補救

  • 刪除緩存重試機制
    image.png
  • 讀取biglog異步刪除緩存
    • 刪除緩存重試機制有一個缺點,對業務線代碼造成大量的侵入
    • 讀取biglog異步刪除緩存方案:專門啟動一個更新緩存程序去訂閱數據庫的binlog,獲得需要操作的數據,然後在進行更新緩存操作
      image.png

歡迎指正文中錯誤

參考文章