實際項目中有可能會使用Redis緩存數據,那么在更新數據的時候如何保證數據庫中的數據和Redis緩存的數據一致,緩存同步策略的選擇是一個很重要的問題。網上有各種說法,大概總結有以下幾種,看看每種方案是否可行以及存在的問題和適用場景。
1、先更新Redis,再更新數據庫 (不可行 )
數據庫是比較復雜的,而且還會涉及事務,因為超時等原因更新操作失敗的可能性較大,這種方案很可能因為數據庫更新失敗,導致緩存和數據庫的數據不一致。
如上圖所示,如果第2步更新數據庫失敗了,那么緩存的數據被更新為20,和數據庫值10不一致了。
這種情況可能會想到補救措施:數據庫更新失敗了再將Redis數據做逆向操作進行回退,但是如果Redis數據回退操作也失敗了呢?甚至還要繼續針對這種失敗做重試?顯然事情越做越復雜了,這種方式不可行。
2、先更新數據庫,再更新Redis (部分場景可用,不推薦 )
上面的方案不可取,這種先更新數據庫的是否就可行呢?假設有兩個請求更新數據,時序圖如下:
如果是并發量不高,對一致性要求沒有特別高時,如上圖更新完數據庫再更新Redis沒有問題。
如果是并發量較高,如圖所示這種場景下雖然請求1和請求2先后完成數據庫更新,但更新緩存時卻是請求2和請求1的順序,那就很可能會把舊數據更新到緩存中導致數據不一致。
3、先刪除Redis,再更新數據庫,訪問的時候再加載數據到緩存 (不可行 )
這里同樣以兩個請求的場景為例,時序圖如下:
從圖上可以看到,當并發場景下如果請求1更新耗時較長,還未來得及更新數據庫中的值,請求2已經先讀取了舊值并加載到緩存中了。這種也會導致兩邊數據不一致,而且并發量很高的時候這種概率也會更大。
針對這個方案存在的問題可以在請求1更新完數據庫后再對Redis做一次刪除操作。也就是緩存雙刪
4、先刪除Redis,再更新數據庫,再刪除Redis,訪問的時候再加載數據到緩存(緩存雙刪)
這種方案一定程度上解決了方案4數據不一致的問題,但是也有一個關鍵點,如上圖所示必須保證第6步刪除緩存操作在第5步回寫入緩存操作之后執行,否則還是會有問題。那么這又引出了另外兩個問題:
- 問題一:如何保證第二次刪除緩存一定在回寫后面執行呢?
關于雙刪的這個問題網上有方案是讓請求1刪除緩存時等待xxx毫秒,這個方案似乎可行,但是這個時間不好控制還是會存在一定風險。
另外也有博主給出的建議方案是將刪除請求加入消息隊列,異步串行化處理刪除
- 問題二:如果雙刪失敗了怎么辦?
同樣的網上也有方案:給redis加一個緩存過期時間、刪除加入消息隊列利用消息隊列的重試機制、自己記錄刪除失敗進行重試等
5、先更新數據庫,再刪除Redis,訪問的時候再加載數據到緩存
這種方案除了請求2第一次查詢這一次不一致,還有另一個極端場景會存在數據不一致。請求1更新數據節點緩存剛好失效了,另一個請求2剛好這時讀取緩存沒有進而讀了數據庫舊值,如下圖所示:
這個極端場景需要滿足緩存更好失效,而且請求2讀取數據庫及回寫緩存耗時較請求1更新數據庫更長,發生的概率非常小,可以忽略,所以相比較而言,方案5是最推薦的處理策略。
通過以上幾種處理方案的分析,可以看到不管哪種方案都存在一定的問題,在滿足實時性的條件下,盡量保證不一致出現的概率更低,或者不一致持續的時間非常短暫,避免長期不一致。非要滿足強一致性可能就需要考慮使用鎖的方案了。
總的來說,針對緩存同步更推薦的方式是,緩存中的數據不由數據更新操作主動觸發,統一在需要使用的時候按需加載,數據更新后及時刪除緩存中的數據。