數據庫和緩存如何保持一致性


數據庫和緩存如何保持一致性

問題引入

大量的訪問請求使得數據庫操作頻繁,結果導致服務器性能下降,為了解決該問題可以引入redis,讓其作為數據庫的緩存。這樣,在客戶端請求數據時,能從緩存中讀取就可以不必去數據庫中讀取,從而減輕數據庫壓力,提高服務器性能。但是如果數據發生變化,而數據又存在於數據庫和redis中,此時就會產生數據一致性問題。

方案一:先更新數據庫,再更新緩存

首先結論是該方案不能解決數據一致性問題,原因出在並發上。比如現在有 A 請求和 B 請求,這兩個請求同時更新同一條數據,此時如果出現如下順序:

  1. A 請求先將數據庫數據更新為 1
  2. B 請求將數據庫數據更新為 2
  3. B 請求將緩存更新為 2
  4. A 請求將緩存更新為 1

此時,數據庫中的數據為 2,而緩存中數據為 1,出現數據不一致的問題,所以該方案不成立。

方案二: 先更新緩存,再更新數據庫

該方案依然不成立,道理如方案一種的案例一樣,只不過將數據庫和緩存換一下位置,最后依然會出現數據不一致。

旁路緩存策略(Cache Aside)

不更新緩存,而是刪除緩存中的數據,然后,到讀取數據時,發現緩存中沒有數據后再去數據庫中讀取數據並更新到緩存中。

此時又引申出一個問題,是先刪除緩存再更新數據庫還是先更新數據庫再刪除緩存。

方案三: 先刪除緩存,再更新數據庫

結論是改方案不成立。假設有一用戶年齡為20, A 請求更新該用戶年齡為21,B 請求讀取該用戶年齡,此時出現如下順序:

  1. A 請求刪除緩存
  2. B 請求緩存未命中,讀取數據庫中年齡為 20,並將 20 更新到緩存中
  3. A 請求更新數據庫中年齡為 21

此時,數據庫中年齡為 21,而緩存中數據為 20,出現數據不一致問題。對於此情況有如下解決方案:

# 刪除緩存
redis.delKey(X)
# 更新數據庫
db.update(X)
# 睡眠
Thread.sleep(N)
# 再刪除緩存
redis.delKey(X)

睡眠操作主要是為了確保 A 請求在睡眠的時候, B 請求能夠在這一段時間完成所有操作。最后再刪掉緩存數據,所以 A 請求的睡眠時間要大於 B 請求從數據庫讀數據+寫入緩存的時間。但是具體睡眠多久是個玄學問題,很難評估出來,所以這個方案也只是盡可能保證數據一致性而已,還是會有數據不一致的問題,因此更建議使用方案四。

方案四: 先更新數據庫,再刪除緩存

假設開始年齡數據在緩存中不存在,A 請求讀取年齡,B 請求更新年齡,此時出現如下順序:

  1. A 請求緩存未命中,讀取數據庫中年齡為 20
  2. B 請求更新數據庫年齡為 21
  3. B 請求刪除緩存
  4. A 請求將 20 寫入緩存

此時,緩存中數據為 20, 而數據庫中數據為 21,出現數據不一致問題。但是在實際中,這個問題出現的概率並不高,因為緩存的寫入通常要遠遠快於數據庫的寫入,所以認為該方案是可以保證數據一致性的。

同時也可以給緩存加上過期時間,萬一出現數據不一致,一旦數據過期被移除緩存,之后請求數據就需要從數據庫中取數據,然后把數據同步到緩存中進而保持數據一致性。

但是此時還存在其他問題,即刪除緩存的操作不一定成功,這就會導致客戶端得到的是未更新的數據。現在有如下兩種方案解決這個問題。

1. 重試機制

引入消息隊列,將要從緩存中刪除的數據加入到消息隊列中。

  • 如果應用刪除緩存失敗,就從消息隊列中重新讀取數據,然后再次刪除緩存,這個就是重試機制。如果重試超過一定次數還是沒有成功,就需要向業務層發送報錯信息了。
  • 如果刪除緩存成功,就要把數據從消息隊列中移除,避免重復操作。

2. 訂閱 MySQL binlog,再操作緩存

在數據庫更新成功后就會產生一條變更日志,記錄在 binlog 中。於是可以通過訂閱 binlog 日志,拿到具體要操作的數據,然后再執行緩存刪除。阿里巴巴的 Canal 中間件就是基於這個實現。

Canal 模擬 MySQL 主從復制的交互協議,把自己偽裝成一個 MySQL 從節點,向 MySQL 主節點發送 dump 請求,MySQL 收到請求后就會開始推送 binlog 給 Canal,Canal 解析 binlog 字節流后,轉換為便於讀取的結構化數據,供下游訂閱使用。

補充

如果在業務中對緩存命中率有很高的要求,此時就需要更新數據庫+更新緩存的方案。為了解決數據不一致問題,現有提供如下兩種做法:

  1. 在更新緩存前先加個分布式鎖,保證同一時間只有一個請求更新數據庫和緩存,就不會產生並發問題了,但引入鎖會對寫入性能帶來影響。
  2. 在更新完緩存時,給緩存加上較短的過期時間,這樣即使出現緩存不一致問題,緩存的數據也會很快過期,對業務還是可以接受的


免責聲明!

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



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