深度剖析如何保證緩存與數據庫的一致性


引言

緩存與數據庫的一致性即更新數據庫中的記錄后,緩存的數據也可要同步更新,不然會讀到臟數據。事實上我們是無法保證緩存與數據庫中的強一致性的,一定會有延遲,我們只能保證其最終一致性。

首先要明確的是,我們不更新緩存的數據,而是刪除緩存,然后由下個請求去去緩存,發現不存在后再讀取數據庫,寫入緩存。因為操作簡單,帶來的副作用也只是一次cache miss而已,刪除緩存可能會因為線程安全的原因導致臟數據,比如線程a,b先后更新數據庫,但是由於網絡阻塞等原因,更新緩存的順序是b,a,從而導致臟數據。

明確了刪除緩存而非更新緩存的原則后,實現一致性無外乎就兩種思路:

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

下面我們深入剖析這兩種思路,看看誰優誰劣?

先緩存后數據庫

考慮這種情況:

(1)請求 A 進行寫操作,刪除緩存

(2)請求 B 查詢發現緩存不存在

(3)請求 B 去數據庫查詢得到舊值

(4)請求 B 將舊值寫入緩存

(5)請求 A 將新值寫入數據庫

上述情況下,即使A刪除了緩存,緩存中依然存在臟數據,如果沒有設置過期時間,這個臟數據永遠不會被清除。

這么看來這種思路並非最優解,但是上有政策下有對策,聰明的程序員們想到了使用“延遲雙刪”來解決這個問題。還是這個問題,使用延遲雙刪是這樣執行的:

(1)請求 A 進行寫操作,刪除緩存

(2)請求 B 查詢發現緩存不存在

(3)請求 B 去數據庫查詢得到舊值

(4)請求 B 將舊值寫入緩存

(5)請求 A 將新值寫入數據庫

(6)請求A休眠一秒,再次刪除緩存

延遲雙刪策略下每次更新數據庫都會二次刪除緩存,確保讀請求結束,寫請求可以刪除讀請求造成的緩存臟數據。

先數據庫再緩存

這種方式同樣會有問題,考慮這種情況:

(1)緩存剛好失效

(2)請求 A 查詢數據庫,得一個舊值

(3)請求 B 將新值寫入數據庫

(4)請求 B 刪除緩存

(5)請求 A 將查到的舊值寫入緩存

但是實際上很難發生這種情況,因為請求A查詢完數據庫一般很快就會寫入緩存,很難等到 請求B更新完數據庫再刪除刪除 還沒寫入緩存。如果真發生這種情況,同樣可以使用延遲雙刪解決。

因此,保證緩存與數據庫一致性一般情況下應先更新數據庫,再刪除緩存。

重試機制

看似問題都解決了,其實還有一個因素沒有考慮到,那就是緩存刪除失敗怎么辦?無論是第一次還是第二次,只要緩存刪除失敗都有可能會造成臟數據未被清空,所以我們需要重試機制保證刪除緩存成功

方案一:異步重試

image-20210925132543872

把需要刪除的key發送至消息隊列,自己消費信息,獲取需要刪除的key進行重試刪除操作,直至成功

這種方案的缺點是需要維護消息隊列,還會對業務代碼造成侵入

方案二:訂閱bin log

image-20210925132918399

更新數據庫數據時,數據庫會將操作信息寫入 binlog 日志當中,訂閱程序提取出所需要的數據以及 key,另起一段非業務代碼,獲得該信息,嘗試刪除緩存操作。發現刪除失敗將這些信息發送至消息隊列 重新從消息隊列中獲得該數據,重試操作。

總結

如果我們要保證緩存與數據庫的一致性,一般情況下選擇先更新數據庫再刪除緩存,配合消息隊列或者訂閱binlog的方式防止刪除緩存失敗。此外,如果對緩存中數據的實時性要求不高,可以等待key過期,這樣也能保證一致性。

參考


免責聲明!

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



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