如何實現 Redis 和 Mysql 中數據雙寫一致性

    在我們的實際開發中,我們用到了 redis 緩存一些常用的數據(如熱點數據)用來提高系統的吞吐量。

    但是不可以避免的出現了數據的修改場景,這就導致了數據庫中的數據和 Redis 中出現不一致性的情況。如何保證數據一致性就顯得非常重要了,下面介紹一下保證數據的雙寫一致性的方案。

1、先刪緩存再操作數據庫方案

    在 redis 一般寫的場景下對數據的更新操作是不推薦使用的,推薦使用刪除緩存數據的操作,因爲刪除操作的效率更高。下圖展示先刪除緩存再操作數據庫的過程圖:

在這種方式下會存在數據不一致的問題,如下圖所示:

(1)線程 1 要更新數據,它先刪除 redis 中的緩存數據,然後由於網絡堵塞導致暫短的停頓,沒有繼續執行操作數據庫 

(2)線程 2 要查詢數據,首先查詢數據庫,但是由於 Redis 中的數據已經被線程 1 刪除了,那麼它會去數據庫中查詢數據 X 並且要將數據 X 同步到 Redis 中

(3)線程 1 網絡堵塞結束,執行了數據庫操作將數據 X 更改爲 Y

    經過上述的過程就導致了 Redis 的數據和數據庫中的數據不一致了,即就是 Redis 中存放的依據是老數據。爲了解決上述的問題,我們採用緩存延遲雙刪的策略,如下圖所示的緩存延遲雙刪的過程:

    採用緩存延遲雙刪策略最多在 X 毫秒內讀取的數據是老數據,在 X 毫秒之後讀取的數據都是最新的數據。X 的具體值如何確定那就需要根據自身的業務了來確定。

    延遲雙刪策略只能保證最終的一致性,不能保證強一致性。由於對 Redis 的操作和 Mysql 的操作不是原子性操作,所以如果想保證數據的強一致性就需要加鎖控制,如下圖所示:

    加鎖之後勢必會帶來系統的吞吐量的下降,所以需要衡量利弊來確定是否使用加鎖。

2、先操作數據庫再刪除緩存方案

    此方案就是先操作數據庫,數據庫寫入成功之後再來刪除 Redis 緩存中的數據。多個線程之間的數據讀取和更新如下圖所示:

    這種方案下,在數據庫更新成功後到刪除 Redis 緩存數據之前的這段時間中,其他線程讀取的數據都是舊數據,等 Redis 刪除緩存後會重新從數據庫中讀取最新數據同步到 Redis,這樣可以在一定程度上保證數據的最終一致性。極端情況下會出現數據不一致的情況,如下圖所示:

(1)線程 1 先成功的更新數據到數據庫中,然後執行刪除 Redis 緩存中的數據的時候失敗了

(2)線程 2 要讀取數據,此時優先從 Redis 中查詢數據,由於此時 Redis 中老數據沒有刪除,所以線程 2 可以拿到舊數據直接返回。直到 Redis 中緩存的數據過期之後纔可以從數據庫中獲取最新的到 Redis 中

3、刪除重試機制

    無論是先刪除緩存再操作數據還是先操作數據庫再刪除緩存的機制,都有可能會出現刪除緩存失敗的情況,如下圖所示:

    爲了應對刪除緩存失敗的情況發生,於是加入了刪除重試機制,如下圖所示:

    通過 canal 監聽 binlog 感知數據的變動後,canal 客戶端執行刪除 Redis 緩存數據,如果緩存數據刪除失敗那麼發送一條 MQ 消息讓 canal 客戶端繼續執行刪除操作,這樣可以保證數據的最終一致性。但是這樣也增加了系統的複雜性。

總結:

(1)實際開發中推薦使用先操作數據庫再刪除緩存的方案,因爲此方案最大程度上保證了數據的一致性並且實現也最簡單。

(2)無論是先操作數據庫再刪除緩存還是先刪除緩存再操作數據庫都有可能會出現刪除緩存失敗的情況,所以需要加入刪除重試機制。

(3)如果想要 Redis 和 Mysql 的數據強一致性,可以考慮使用加鎖的方式實現。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/1xxXepmhTVoN4_EPb7QdUg