緩存刷新術之保證數據一致性

1. 概述

在上一篇文章《緩存讀取術之防止緩存雪崩》裏我們解決了引入緩存後讀數據的問題,本文分析寫數據要考慮的問題。數據變更時是更新緩存還是淘汰緩存?是先寫DB再寫Cache,還是先寫Cache再寫DB?如何考量?另外,如果寫DB成功了但寫Cache失敗了,數據就會不一致,如何解決?下面逐一探討。

本文討論的緩存更傾向於分佈式緩存,不過解決方案的思路對本地緩存而言也是大致適用的

2. 更新緩存還是淘汰緩存 ?

爲方便討論,我們先假設更新數據時,先操作數據庫,再操作緩存。現在有2個併發的寫操作,看下“更新”緩存會怎樣,如下圖

這裏寫圖片描述

  • 應用A先發起更新DB的操作A-1,應用B後發起操作B-1,都成功後DB的數據此時是B-1操作後的數據;
  • 因爲某種原因,應用B更新緩存(B-2操作)要快於應用A(A-2操作),從而後到的A-2操作會覆蓋掉B-2的數據,導致緩存最終的數據是A-2更新的數據,這樣DB和緩存的數據就不一致。

如果選擇淘汰緩存的策略,就不存在這種併發下覆蓋數據的問題。A-2和B-2都會把緩存刪掉,誰先誰後都沒關係,下次讀的時候自然會重新加載。

結論:淘汰緩存。

3. 先操作DB還是先操作緩存 ?

因爲我們採用了淘汰緩存的策略,所以問題變成先操作DB還是先淘汰緩存。來考慮下先淘汰緩存會怎樣。假設一個寫操作和一個讀操作併發出現(這是很常見的情形),如下圖

這裏寫圖片描述

  • 先發生寫操作,應用A淘汰了緩存(W-1),在W-2沒完成之前,應用B的讀操作進來了。
  • R-1操作讀取緩存,發現沒有就去讀DB(R-2操作),然後把數據寫入緩存。
  • 應用A的W-2操作完成,把新的數據更新到DB。這樣就導致DB和Cache數據不一致。

由於寫操作往往比讀操作更加耗時,所以上面的併發情況還是很有可能出現的。

那麼先操作DB又如何呢?

首先類似上面的併發順序下是沒有問題的:應用A先更新DB,在淘汰緩存之前,有讀操作發生,應用B拿到cache中的舊值並返回,然後應用A淘汰了緩存。那麼下次的讀操作就能獲取到新值,緩存也會被更新。是沒有問題的。

再考慮這種併發順序:

這裏寫圖片描述

  • 應用B讀操作先到(R-1操作),此時緩存爲空,R-2操作讀取DB。R-2成功執行。在把數據寫到緩存以前(R-3操作),應用A的寫操作進來了。
  • 應用A更新DB(W-1操作),淘汰緩存(W-2操作)都已成功執行。接着應用B的R-3操作才執行。這樣緩存的數據就是舊的值,跟DB的新值就不一致。

這種情況雖然有可能發生,但概率極低。因爲要滿足的條件非常苛刻:寫操作(W-1,W-2)要剛好在R-2操作完成之後進來,並且要比一個寫緩存操作(R-3)更快完成。寫DB一般比寫緩存要慢,所以上面是小概率事件。

結論:先操作DB後淘汰緩存。

4. 寫DB成功但淘汰緩存失敗怎麼辦?

如下圖:

這裏寫圖片描述

第一步更新DB成功,但第二步淘汰緩存卻失敗了。也會數據不一致。怎麼辦?可以對DB進行回滾操作,這在程序中是比較容易實現的,把寫DB和寫緩存放在一個事務內,捕獲到寫緩存的異常就回滾DB操作。也可以先重新若干次“淘汰緩存”操作後再考慮是否回滾。

5. 小結

經過上面的分析,爲最大程度保證DB和緩存的數據一致性,顯然我們可以優先考慮的策略是:

  • 先更新DB後淘汰緩存
  • 如果更新DB成功,但淘汰緩存失敗,則回滾DB操作

知識共享許可協議
本作品採用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章