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