根據(jù)當前計算機系統(tǒng)的三層儲存架構(gòu),cpu緩存,內(nèi)存,磁盤,我們?nèi)粘i_發(fā)通常會模仿這三層儲存架構(gòu)而在數(shù)據(jù)庫之上添加機器本地緩存及redis緩存,既然涉及到了多個儲存系統(tǒng),那么必然就有數(shù)據(jù)一致性問題。
根據(jù)redis緩存的使用,可以分成2種情況:只讀緩存和讀寫緩存
1.只讀緩存
當系統(tǒng)收到get請求,則查詢redis,redis有數(shù)據(jù)則返回,無數(shù)據(jù)則查詢數(shù)據(jù)庫,查到之后,寫入緩存,以便于以后的請求使用。至于寫請求,直接操作數(shù)據(jù)庫。這樣做的好處就是,保證了最新數(shù)據(jù)都在數(shù)據(jù)庫中,而數(shù)據(jù)庫是有可靠性保障的。
2.讀寫緩存
寫請求也會發(fā)送到redis,得益于redis的高性能,寫請求能快速完成,但是如果數(shù)據(jù)沒有寫入數(shù)據(jù)庫,便發(fā)生宕機或者掉電,那么會發(fā)生數(shù)據(jù)丟失,給業(yè)務帶來風險。所以通常會有兩種回寫策略。
2.1同步回寫
由于數(shù)據(jù)庫和redis的速度差異,同步回寫會降低系統(tǒng)的響應速度。
2.2異步回寫
所有的寫操作先在緩存中更改,優(yōu)先考慮系統(tǒng)響應,同樣的,reids的宕機或者掉電則會導致數(shù)據(jù)丟失。
3.數(shù)據(jù)一致性
分為兩種情況,一是 緩存中有數(shù)據(jù),則必須與數(shù)據(jù)庫中的數(shù)據(jù)一致;二是 緩存中沒數(shù)據(jù),則數(shù)據(jù)庫中的數(shù)據(jù)必須是最新的。
對于讀寫緩存而言,根據(jù)具體場景對一致性的要求,選擇同步或者異步回寫。
對于讀緩存而言,數(shù)據(jù)的一致性的復雜程度,主要發(fā)生在數(shù)據(jù)的刪除和修改,新增的話,直接操作數(shù)據(jù)庫,故無一致性問題。
3.1刪除和修改數(shù)據(jù)
第一種情況:我們先刪除緩存,在更新數(shù)據(jù)庫,潛在的問題:數(shù)據(jù)庫更新失敗了,get請求進來發(fā)現(xiàn)沒緩存則請求數(shù)據(jù)庫,
導致緩存又刷入了舊的值。
第二種情況:我們先更新數(shù)據(jù)庫,再刪除緩存,潛在的問題:緩存刪除失敗,get請求進來緩存命中,導致讀到的是舊值
3.2先刪除緩存再更新數(shù)據(jù)庫
假設(shè)有2個線程A和B,A刪除緩存之后,由于網(wǎng)絡(luò)延遲,在更新數(shù)據(jù)庫之前,B來訪問了,發(fā)現(xiàn)緩存未命中,則去請求數(shù)據(jù)庫然后把舊值刷入了緩存,這時候姍姍來遲的A,才把最新數(shù)據(jù)刷入數(shù)據(jù)庫,導致了數(shù)據(jù)的不一致性
3.2.1延遲雙刪
我們可以在A更新完數(shù)據(jù)庫之后,sleep一會,再刪一次緩存,需要注意的是,你要sleep到線程B把舊值刷到了緩存里面你再醒來把緩存刪了,不然就會又被線程B把舊的值刷進去,所以需要預估讀緩存和寫緩存的時間以此確認應當sleep多久
3.3先更新數(shù)據(jù)庫再刪除緩存
這種場景潛在的風險就是更新完數(shù)據(jù)庫,刪緩存之前,會有部分并發(fā)請求讀到舊值,這種情況對業(yè)務影響較小,我們通過重試機制,保證緩存能得到刪除