「系統架構」緩存與數據庫的數據一致性方案介紹
在很多系統中重要數據通常都是寫入關系數據庫如mysql中,為了實現讀寫分離,提高系統負載能力,縮短響應時間通常還需要用到緩存。
緩存帶來了系統性能的提升同時也把數據一致性問題擺在了開發者面前,在數據庫使用讀寫分離和主從同步的情況下這種一致性問題會變得更加復雜。本文將介紹幾種提升一致性的方案供大家參考。
背景介紹
一般使用緩存(本文中的緩存不特指某一種分布式緩存或本地緩存)的方式為在讀數據時首先讀取緩存,如果緩存沒有則讀數據庫然后將數據寫入緩存最后返回;寫數據時首先清除緩存內的數據,然后寫數據庫。
這種方式在數據庫配置了主從庫時會遇到數據不一致的問題,首先來看一下這種實現的具體流程如下圖:
圖1 主從數據不一致
在讀數據時如果緩存中沒有數據則讀取從庫的數據,然后寫入緩存中并返回;在寫數據時先清除緩存中的數據,然后將數據寫入主庫,主庫數據會被同步到從庫。
這種實現方式的主要問題在于當數據寫入主庫后,緩存沒有數據,這時讀請求會讀取從庫的數據。此時如果發生主從延遲,主庫的數據還沒有寫到從庫,則應用服務器會將從庫讀到的臟數據寫入緩存服務器中,如果寫入的數據沒有加有效期或有效期很長就會造成數據不一致,如果主從延遲時間較長可能會導致大面積的數據不一致。下面將介紹幾種解決數據一致性問題的方案。
加有效期
給緩存中的數據增加有效期是解決一致性問題最簡單的確保數據最終一致性的方法,這種方法在緩存中沒有數據需要查詢數據庫時將查詢結果放入緩存的時候設置一個有效期(更新數據時仍然先清除緩存數據),非常適用于更新頻率較低的數據,例如商品信息。
但是單純給數據加上有效期也存在一些明顯的問題,如果有效期較長就會出現上面提到的數據不一致的問題,如果有效期較短就會出現緩存效率不高經常讀庫的情況。在使用這種方法的時候就需要我們根據數據的更新頻率確定合適的有效期時間,當冷熱數據并存時這種方案就顯得難以兼顧。那么什么策略既能確保數據的最終一致性又能充分利用緩存呢?這就要提到業內使用最多的雙淘汰了。
雙淘汰
雙淘汰與本文第一個方案相比在讀取數據時是相同的,區別在于更新數據的流程。在更新數據時仍然首先清除緩存的數據,然后將數據寫入到數據庫中,然后將數據記錄在一個延遲隊列或哈希表中,同時另一個線程不斷讀取延遲隊列或者哈希表,根據數據存入的時間也預先設定的延遲時間再次清除緩存了的數據。
可以看出預先設定的延遲時間應該大于數據庫主從同步較慢情況下的同步時間,這樣就能確保在主從延遲的情況下緩存中的臟數據也能被清除保證了數據一致性。流程如下圖,C語言中可以使用哈希表實現。
雖然雙淘汰保證了數據的最終一致性并提高了緩存的使用率,但在兩次“淘汰”之間讀取的數據仍然有可能是臟數據,這種情況會在主從延遲較長的情況下尤為明顯。對于某些對實時一致性要求較高的系統如何獲得更好的讀一致性呢,這里需要提到雙淘汰的另一種變型。
圖2 雙淘汰
另一種雙淘汰
為了提高讀到數據的準確性這種方法在更新數據時首先清除緩存數據并在緩存中存入這個數據對應的標記,在寫入數據庫成功后再將數據寫入到一個延遲隊列或哈希表中。在讀取數據時從緩存讀取數據,如果存在直接返回,如果不存在則讀取數據對應的標記,如果標記存在則讀主庫否則讀從庫,最后將數據寫入緩存中。
同時另一個線程不斷讀取延遲隊列或者哈希表,根據數據存入的時間也預先設定的延遲時間再次清除緩存了的數據。整個讀寫過程如下圖??梢钥闯鲞@種方法通過在緩存增加一個標記將部分讀請求分流到了主庫,這個標記可以是數據的主鍵或其他唯一標識,通過犧牲一部分主庫的性能提高了讀請求的數據一致性。
圖3 雙淘汰2
目前為止沒有哪一種緩存策略是萬能的,基本上我們仍需要根據具體的業務場景和數據類型選擇合適的緩存策略。數據量越大數據情況也復雜通常就需要越復雜的緩存策略,希望本文介紹的幾個方案對讀者今后的開發有所幫助。