緩存和DB的數據不一致主要有兩種情況:
- 併發的場景下,導致讀取舊的 DB 數據,更新到緩存中。
- 緩存和 DB 的操作,不在一個事務中,可能只有一個操作成功,而另一個操作失敗,導致不一致。
常用的優化方案,主要是解決兩個問題:
- 將緩存可能存在的並行寫,實現串行寫。
- 實現數據的最終一致性。
下面是我們比較常用到的集中優化手段:
1、先淘汰緩存,再寫數據庫,注意要引入分佈式鎖,從而實現串行寫的目的。
- 在寫請求時,先淘汰緩存之前,獲取該分佈式鎖。
- 在讀請求時,發現緩存不存在時,先獲取分佈式鎖。
2、先寫數據庫,再更新緩存
這時候需要保證 DB 和緩存的操作在“同一個事務”中,從而實現最終一致性。
下面有兩種實現方式:
① 基於定時任務來實現
- 首先,寫入數據庫。
- 然後,在寫入數據庫所在的事務中,插入一條記錄到任務表。該記錄會存儲需要更新的緩存 key和 value。
- 最後,定時任務每秒掃描任務表,更新到緩存中,之後刪除該記錄。
② 基於消息隊列來實現
- 先寫入數據庫。
- 然後,在發送帶有緩存key和value 的事務消息。比如阿里的 RocketMQ 就支持消息事務。
- 最後,消費者消費該消息,更新到緩存中。
3、基於數據庫的 binlog 日誌
- 數據直接寫入數據庫。
- 數據庫更新binlog日誌(注意:數據庫要開啓binlog爲row模式,因爲canal讀取binlog日誌格式要求如此)。
- 利用阿里的Canal中間件讀取binlog日誌。
- Canal藉助於限流組件按頻率將數據發到MQ中。
- 將MQ的數據更新到Redis緩存中。
雖然這種方式實現了緩存與DB的一致性,但是需要引入多餘的第三方組件來實現,實際項目還是要根據具體的需求來確定是否使用該方案。在之前的做過的一個項目中,我們就是使用了mysql, canal, kafka, Elasticsearch, 實現了Elasticsearch與mysql之間的數據一致性。
參考:https://www.w3cschool.cn/architectroad/architectroad-consistency-of-cache-with-database.html