mysql數據庫和redis雙寫不一致方案討論 ,如何保證數據庫和緩存雙寫一致性?
緩存和數據庫之間所有的策略關系如下:
在高并發應用場景下,如果是對數據一致性要求高的情況下,要定位好導致數據和緩存不一致的原因。
解決高并發場景下數據一致性的方案有兩種,分別是延時雙刪策略和異步更新緩存兩種方案。
另外,設置緩存的過期時間是保證數據保持一致性的關鍵操作,需要結合業務進行合理的設置。
第一種:延時雙刪
一、先刪除緩存,再更新數據庫
1.如果先刪除Redis緩存數據,然而還沒有來得及寫入MySQL,另一個線程就來讀取。發現緩存為空,則去Mysql數據庫中
讀取舊數據寫入緩存,次實緩存數據為臟數據,然后更新數據庫后,發現Redis和Mysql數據不一致
一、先更新數據庫,再刪除緩存
1.如果先更新數據庫,但是寫庫成功,此時線程掛掉了,導致刪除緩存失敗,這個時候其他線程讀取數據庫就是舊數據庫
因為寫和讀是并發的,沒法保證順序,就會出現緩存和數據庫的數據不一致的問題
解決方案:延時雙刪
[在寫庫前后都進行redis.del(key)操作,并且設定合理的超時時間
public void write (String key, Object data ){
redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
具體步驟
1、先刪除緩存
2、再寫數據庫
3、休眠500毫秒
4、再次刪除緩存
問題:這個500毫秒怎么確定的,具體該休眠多久時間呢?
1、需要評估自己的項目的讀數據業務邏輯的耗時。
2、這么做的目的,就是確保讀請求結束,寫請求可以刪除讀請求造成的緩存臟數據。
3、當然這種策略還要考慮redis和數據庫主從同步的耗時。
4、最后的的寫數據的休眠時間:則在讀數據業務邏輯的耗時基礎上,加幾百ms即可。
比如:休眠1秒。
設置緩存過期時間是關鍵點
1、從理論上來說,給緩存設置過期時間,是保證最終一致性的解決方案
2、所有的寫操作以數據庫為準,只要到達緩存過期時間,緩存刪除
3、如果后面還有讀請求的話,就會從數據庫中讀取新值然后回填緩存
方案缺點
結合雙刪策略+緩存超時設置,這樣最差的情況就是:
1、在緩存過期時間內發生數據存在不一致
2、同時又增加了寫請求的耗時。
第二種:異步更新緩存(基于Mysql binlog的同步機制)
整體思路
1、涉及到更新的數據操作,利用Mysql binlog 進行增量訂閱消費
2、將消息發送到消息隊列
3、通過消息隊列消費將增量數據更新到Redis上
4、.操作情況
讀取Redis緩存:熱數據都在Redis上
寫Mysql:增刪改都是在Mysql進行操作
更新Redis數據:Mysql的數據操作都記錄到binlog,通過消息隊列及時更新到Redis上
Redis更新過程
數據操作主要分為兩種:
1、一種是全量(將所有數據一次性寫入Redis)
2、一種是增量(實時更新)
這里說的是增量,指的是mysql的update、insert、delate變更數據。
讀取binlog后分析 ,利用消息隊列,推送更新各臺的redis緩存數據。
1、這樣一旦MySQL中產生了新的寫入、更新、刪除等操作,就可以把binlog相關的消息推送至Redis
2、Redis再根據binlog中的記錄,對Redis進行更新
3、其實這種機制,很類似MySQL的主從備份機制,因為MySQL的主備也是通過binlog來實現的數據一致性
這里的消息推送工具你也可以采用別的第三方:kafka、rabbitMQ等來實現推送更新Redis!
以下是具體問題分析:
1.先更新數據庫,再更新緩存(一般不使用)
解釋:如果緩存數據不是直接從數據庫中查詢出來的,而是需要經過一系列的運算,這樣更新緩存的代價很高,如果此時有很多請求對數據庫進行 寫數據的操作但是讀請求并不多,那么此時如果每次寫請求都更新一下緩存,那么性能損耗是非常大的,如果讀多寫多則更不可取,可能出現數據不一致的情況
2.先更新數據庫,再刪除緩存問題(可以將刪除的任務放入MQ)
解釋:這一種情況也會出現問題,比如更新數據庫成功了,但是在刪除緩存的階段出錯了沒有刪除成功,那么此時再讀取緩存的時候每次都是錯誤的數據了。
3.先更新緩存,再更新數據庫(一般不使用)
解釋:這種情況與第一種類似,所有緩存更新的操作都是不可取的,同時面臨突然很多寫請求很多時,緩存更新性能消耗代價太高同時可能造成數據不一致
4.先刪除緩存,再更新數據庫(刪除緩存失敗問題,延時雙刪)
先刪除緩存,再更新數據庫
此時來了兩個請求,請求 A(更新操作) 和請求 B(查詢操作)請求 A 會先刪除 Redis 中的數據,然后去數據庫進行更新操作此時請求 B 看到 Redis 中的數據時空的,會去數據庫中查詢該值,補錄到 Redis 中但是此時請求 A 并沒有更新成功,或者事務還未提交
先刪除緩存,再更新數據庫問題解決方案:延時雙刪
數據不一致最關鍵的點:就是更新操作的執行流程提交事務過程中,是否有查詢請求,搶先執行完畢的情況,過程中也可以強制加鎖,保證更新操作完成,才能進行數據查詢
先刪除緩存,再更新數據庫解決方案:延時雙刪,Mysql讀寫分離架構延時問題
問題延伸:但是上述的保證事務提交完以后再進行刪除緩存還有一個問題,就是如果你使用的是 Mysql 的讀寫分離的架構的話,那么主從延時問題
此時來了兩個請求,請求 A(更新操作) 和請求 B(查詢操作)請求 A 更新操作,刪除了 Redis請求主庫進行更新操作,主庫與從庫進行同步數據的操作請 B 查詢操作,發現 Redis 中沒有數據去從庫中拿去數據此時同步數據還未完成,拿到的數據是舊數據
先刪除緩存,再更新數據庫解決方案:延時雙刪+讀寫分離架構(強制讀主庫)解決
先更新數據庫,再刪除緩存解決數據一致性方案,利用消息隊列進行刪除的補償
此時解決方案就是利用消息隊列進行刪除的補償。具體的業務邏輯用語言描述如下:請求 A 先對數據庫進行更新操作在對 Redis 進行刪除操作的時候發現報錯,刪除失敗此時將Redis 的 key 作為消息體發送到消息隊列中系統接收到消息隊列發送的消息后再次對 Redis 進行刪除操作但是這個方案會有一個缺點就是會對業務代碼造成大量的侵入,深深的耦合在一起,所以這時會有一個優化的方案,我們知道對 Mysql 數據庫更新操作后再 binlog 日志中我們都能夠找到相應的操作,那么我們可以訂閱 Mysql 數據庫的 binlog 日志對緩存進行操作。