淺談緩存模式
緩存作為實際開發(fā)中高頻出現(xiàn)的基礎(chǔ)組件,這里簡單談?wù)勈褂镁彺娴膸追N典型模式

Cache Aside Pattern
Cache Aside模式,是我們?nèi)粘i_發(fā)中經(jīng)常使用的緩存模式。該模式的目的在于保證并發(fā)的前提下盡可能減少(緩存與數(shù)據(jù)庫間)數(shù)據(jù)不一致的可能,其是對AP模型下Base理論的體現(xiàn)。具體來說,在該模式下對于數(shù)據(jù)的讀操作,步驟如下:
首先從緩存中讀取數(shù)據(jù) 如果命中緩存Cache Hit,則直接進(jìn)入步驟4 如果未命中緩存Cache Miss,則從數(shù)據(jù)庫讀取數(shù)據(jù),并放入緩存中 返回數(shù)據(jù)結(jié)果
而對于數(shù)據(jù)的寫操作,其步驟流程就很簡單了
更新數(shù)據(jù)庫,即將數(shù)據(jù)寫入數(shù)據(jù)庫 刪除緩存中相應(yīng)的數(shù)據(jù)
顯然這里對于讀操作的流程步驟沒有什么爭議。問題在于寫操作中為什么是先寫數(shù)據(jù)庫、再刪緩存?能不能先刪除緩存再寫數(shù)據(jù)庫?答案顯然是不能。如下圖所示,當(dāng)寫操作剛刪除完緩存中的數(shù)據(jù)時,恰好此時有一個新的讀請求。其讀取數(shù)據(jù)庫,并設(shè)置緩存。此時緩存中Name的值為Aaron。然后寫操作開始寫數(shù)據(jù)庫,將數(shù)據(jù)庫中的Name修改為Bob。可以看到此時緩存與數(shù)據(jù)庫中的數(shù)據(jù)發(fā)生不一致

還有人可能會問,為什么在Cache Aside模式的寫操作過程中,為什么不在寫完數(shù)據(jù)庫后、再去寫緩存呢?這樣不是可以提高下一次讀操作的效率么?一方面,有些場景下計算緩存值的成本可能較高,如果本次寫緩存后。下次又是一次寫操作,則會浪費之前所計算的緩存。故緩存的寫入放在讀操作中進(jìn)行,即所謂的懶加載。另一方面,其同樣會導(dǎo)致數(shù)據(jù)的一致性問題。如下圖所示,有兩個請求分別進(jìn)行寫操作。當(dāng)寫操作#1剛剛寫完數(shù)據(jù)庫,此時DB中Age的值為22。此時另外一個寫操作#2則將數(shù)據(jù)庫、緩存均更新為18?,F(xiàn)在寫操作#1開始寫緩存,可以看到當(dāng)其操作結(jié)束后。數(shù)據(jù)庫、緩存中Age的值出現(xiàn)了不一致

前面提到了Cache Aside模式只能盡可能地減少發(fā)生數(shù)據(jù)不一致的問題,其在極端情況下依然有可能會出現(xiàn)數(shù)據(jù)不一致。如下圖所示,比如在某請求在進(jìn)行讀操作時,緩存中并無相應(yīng)數(shù)據(jù)。造成該現(xiàn)象的原因有可能是因為此前剛剛完成了一次寫操作,把緩存刪掉了;也有可能是因為是第一次訪問,緩存無數(shù)據(jù)所導(dǎo)致的。總之該請求的讀操作需要從數(shù)據(jù)庫中讀取,這里記為200。這時突然有另外一個請求,即寫操作#2。其迅速完成了數(shù)據(jù)庫的寫入將其更新為300,并同時刪除了緩存。而此時讀操作才開始將其此前讀到的200寫入緩存。顯然此時緩存與數(shù)據(jù)庫的數(shù)據(jù)發(fā)生了不一致

甚至可以說,在數(shù)據(jù)庫使用主從架構(gòu)時,如果從庫的同步延遲較高,即雖然針對主庫的寫請求很快完成了寫數(shù)據(jù)庫、刪除緩存的操作;但此時從庫的數(shù)據(jù)依然還沒有被同步更新過來,導(dǎo)致對從庫的讀操作訪問到了舊值并回寫到了緩存當(dāng)中。此時同樣出現(xiàn)了數(shù)據(jù)的不一致性問題。故針對類似這種極端場景,可以考慮在基礎(chǔ)的Cache Aside(即寫數(shù)據(jù)庫、刪緩存)完成后,經(jīng)過一定的延遲時間后再進(jìn)行一次緩存刪除操作,以解決讀操作把舊值寫入緩存的問題。即所謂的「延遲雙刪」。當(dāng)然這里第二次刪除所需的延遲時間,需要根據(jù)實際業(yè)務(wù)場景來決策
Read/Write Through Pattern
在Cache Aside模式中,我們需要同時操作數(shù)據(jù)庫和緩存,以保持二者間數(shù)據(jù)的同步。而在Read/Write Through模式下,則簡單很多。我們只需對緩存服務(wù)進(jìn)行讀、寫即可。由緩存服務(wù)內(nèi)部實現(xiàn)與數(shù)據(jù)庫數(shù)據(jù)的同步。換言之,在該模式下可以將緩存服務(wù)視為數(shù)據(jù)庫的門面/代理。典型地,Ehcache支持該緩存模式
Write Behind Pattern
在Write Through模式中,當(dāng)進(jìn)行寫入操作時,緩存服務(wù)內(nèi)部是先寫入緩存、再同步到數(shù)據(jù)庫中。此舉顯然大大降低了性能,故提出了Write Behind模式,其不同點就在于。執(zhí)行寫操作時,緩存服務(wù)是先寫入緩存,然后通過異步的方式寫入數(shù)據(jù)庫。典型地,數(shù)據(jù)庫的Buffer Pool緩沖池中就使用了該緩存模式
參考文獻(xiàn)
鳳凰架構(gòu): 構(gòu)建可靠的大型分布式系統(tǒng) 周志明著
