Redis緩存和數據庫雙寫一致方案


Redis 和數據庫讀操作

數據緩存往往會在 Redis 上設置超時時間,當設置 Redis 的數據超時后,Redis 就沒法讀出數據了,這個時候就會觸發程序讀取數據庫,然后將讀取的數據庫數據寫入 Redis(此時會給 Redis 重設超時時間),這樣程序在讀取的過程中就能按一定的時間間隔刷新數據了,讀取數據的流程如圖 2 所示。


讀取數據的流程

下面寫出這個流程的偽代碼:

public DataObject readMethod(args) {
    // 嘗試從Redis中讀取數據
    DataObject data = getRedis(key);
    if(data != null) {
        // 讀取數據返回為空,失敗
        // 從數據庫中讀取數據
        data = getFromDataBase();
        // 重新寫入Redis,以便以后讀出
        writeRedis(key,data);
        // 設置Redis的超時時間為5分鍾
        setRedisExpire(key,5);
    }
    return data;
}

這樣每當讀取 Redis 數據超過 5 分鍾,Redis 就不能讀到超時數據了,只能重新從 Redis 中讀取,保證了一定的實時性,也避免了多次訪問數據庫造成的系統性能低下的情況。

Redis 和數據庫寫操作

寫操作要考慮數據一致的問題,尤其是那些重要的業務數據,所以首先應該考慮從數據庫中讀取最新的數據,然后對數據進行操作,最后把數據寫入 Redis 緩存中,如圖 3 所示。

寫入業務數據,先從數據庫中讀取最新數據,然后進行業務操作,更新業務數據到數據庫后,再將數據刷新到 Redis 緩存中,這樣就完成了一次寫操作。這樣的操作就能避免將臟數據寫入數據庫中,這類問題在操作時要注意。

下面寫出這個流程的偽代碼:

public DataObject writeMethod(args) {
    //從數據庫里讀取最新數據
    DataObject dataObject = getFromDataBase(args);
    //執行業務邏輯
    ExecLogic(dataObject);
    //更新數據庫數據
    updateDataBase(dataObject);
    //刷新Redis緩存
    updateRedisData(dataObject);
}

 

首先,從數據庫中讀取最新的數據,以規避緩存中的臟數據問題,執行了邏輯,修改了部分業務數據。然后,把這些數據保存到數據庫里,最后,刷新這些數據到 Redis 中。

Cache Aside Pattern

寫緩存同步也可以使用刪除key的方式。這個用於可能緩存比較冷,並不常被讀出的情景(內涵就是我都不讀,你頻繁寫有啥好處呢,還不如直接刪了等讀的時候懶加載)

最經典的緩存+數據庫讀寫的模式,就是 Cache Aside Pattern。

  • 讀的時候,先讀緩存,緩存沒有的話,就讀數據庫,然后取出數據后放入緩存,同時返回響應。
  • 更新的時候,先更新數據庫,然后再刪除緩存。

為什么是刪除緩存,而不是更新緩存?

原因很簡單,很多時候,在復雜點的緩存場景,緩存不單單是數據庫中直接取出來的值。

比如可能更新了某個表的一個字段,然后其對應的緩存,是需要查詢另外兩個表的數據並進行運算,才能計算出緩存最新的值的。

另外更新緩存的代價有時候是很高的。是不是說,每次修改數據庫的時候,都一定要將其對應的緩存更新一份?也許有的場景是這樣,但是對於比較復雜的緩存數據計算的場景,就不是這樣了。如果你頻繁修改一個緩存涉及的多個表,緩存也頻繁更新。但是問題在於,這個緩存到底會不會被頻繁訪問到?

舉個栗子,一個緩存涉及的表的字段,在 1 分鍾內就修改了 20 次,或者是 100 次,那么緩存更新 20 次、100 次;但是這個緩存在 1 分鍾內只被讀取了 1 次,有大量的冷數據。實際上,如果你只是刪除緩存的話,那么在 1 分鍾內,這個緩存不過就重新計算一次而已,開銷大幅度降低。用到緩存才去算緩存。

Cache Aside Pattern帶來的臟讀風險

數據發生了變更,先刪除了緩存,然后要去修改數據庫,此時還沒修改。一個請求過來,去讀緩存,發現緩存空了,去查詢數據庫,查到了修改前的舊數據,放到了緩存中。隨后數據變更的程序完成了數據庫的修改。完了,數據庫和緩存中的數據不一樣了...
只有在對一個數據在並發的進行讀寫的時候,才可能會出現這種問題。其實如果說你的並發量很低的話,特別是讀並發很低,每天訪問量就 1 萬次,那么很少的情況下,會出現剛才描述的那種不一致的場景。但是問題是,如果每天的是上億的流量,每秒並發讀是幾萬,每秒只要有數據更新的請求,就可能會出現上述的數據庫+緩存不一致的情況。

解決方案如下:

1、按照先修改數據庫再修改緩存來實現(即不刪除緩存)

2、用zookeeper或者redis建一個key標記,說明這個修改(數據庫)行為還沒結束,先鎖住,等(修改成功)結束了再釋放鎖,讀的時候自然是新的數據,在鎖住的時候讓客戶端延遲讀。


免責聲明!

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



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