title: Redis 與數(shù)據(jù)庫的一致性
date: 2021/05/26 17:00
首先脉顿,給緩存設(shè)置過期時間是可以保證最終一致性的解決方案腺办,所以接下來的方案是保障緩存與數(shù)據(jù)庫數(shù)據(jù)盡可能的更快一致。
方案主要分為以下四種情況:
- 先更新數(shù)據(jù)庫,再更新緩存
- 先更新緩存,再更新數(shù)據(jù)庫
- 先刪除緩存擎场,再更新數(shù)據(jù)庫
- 先更新數(shù)據(jù)庫,再刪除緩存
更新緩存 VS 刪除緩存
更新緩存: 數(shù)據(jù)不但寫入數(shù)據(jù)庫几莽,還會寫入緩存
刪除緩存: 數(shù)據(jù)只會寫入數(shù)據(jù)庫迅办,不會寫入緩存,只會刪除緩存
優(yōu)點(diǎn)對比
- 更新緩存的優(yōu)點(diǎn): 緩存不會增加一次Miss章蚣,命中率高
- 刪除緩存的優(yōu)點(diǎn): 操作簡單礼饱,能防止更新出現(xiàn)的線程安全問題
那到底是選擇更新緩存還是刪除緩存呢,主要取決于更新緩存的復(fù)雜度,如果更新緩存的代價很小镊绪,此時我們應(yīng)該更傾向于更新緩存,以保證更高的緩存命中率洒忧;如果更新緩存的代價很大蝴韭,此時我們應(yīng)該更傾向于刪除緩存。
例如:
- 只是簡單的更新一下用戶余額熙侍,只操作一個字段榄鉴,那就可以采用更新緩存
- 還有類似秒殺下商品庫存數(shù)量這種并發(fā)下查詢頻繁的數(shù)據(jù),也可以使用更新緩存
不過這種更新數(shù)值蛉抓,要注意線程安全的問題庆尘,防止產(chǎn)生臟數(shù)據(jù)
主要還是要以業(yè)務(wù)場景為主進(jìn)行選擇。不過大部分場景下刪除緩存操作簡單巷送,并且?guī)淼母弊饔弥皇窃黾恿艘淮蜟ache Miss驶忌,建議作為通用的處理方式。
先更新數(shù)據(jù)庫笑跛,再更新緩存
這種方式就適合更新緩存的代價很小的數(shù)據(jù)付魔,例如上面說的余額,庫存數(shù)量這類數(shù)據(jù)飞蹂,不過要注意線程安全的問題几苍。
線程安全角度
同時有請求A和請求B進(jìn)行更新操作,那么會出現(xiàn)
- 線程A更新了數(shù)據(jù)庫
- 線程B更新了數(shù)據(jù)庫
- 線程B更新了緩存
- 線程A更新了緩存
這就出現(xiàn)請求A更新緩存應(yīng)該比請求B更新緩存早才對陈哑,但是因?yàn)榫W(wǎng)絡(luò)等原因妻坝,B卻比A更早更新了緩存,這就導(dǎo)致了臟數(shù)據(jù)
業(yè)務(wù)場景角度
有如下兩種不適合場景
如果你是一個寫數(shù)據(jù)庫場景比較多惊窖,而讀數(shù)據(jù)場景比較少的業(yè)務(wù)需求刽宪,采用這種方案就會導(dǎo)致,數(shù)據(jù)壓根還沒讀到爬坑,緩存就被頻繁的更新纠屋,浪費(fèi)性能;但是這種場景做啥緩存呢盾计?
如果你寫入數(shù)據(jù)庫的值售担,并不是直接寫入緩存的,而是要經(jīng)過一系列復(fù)雜的計算再寫入緩存署辉。那么族铆,每次寫入數(shù)據(jù)庫后,都再次計算寫入緩存的值哭尝,無疑是也浪費(fèi)性能的
先更新緩存哥攘,再更新數(shù)據(jù)庫
本來這種情況應(yīng)該是和第一種情況一樣會存在線程安全問題的,但是這種情況是有人使用過的,根據(jù)書籍《淘寶技術(shù)這十年》里逝淹,多隆把商品詳情頁放入緩存耕姊,采取的正是先更新緩存,再將緩存中的數(shù)據(jù)異步更新到數(shù)據(jù)庫這種方式栅葡,有興趣了解的可以查看這篇博客: https://www.cnblogs.com/rjzheng/p/9240611.html
還有現(xiàn)在互聯(lián)網(wǎng)常見的點(diǎn)贊功能茉兰,也可以采用這種方式,有興趣了解的可以查看這篇文章: https://juejin.im/post/5bdc257e6fb9a049ba410098
先刪除緩存欣簇,再更新數(shù)據(jù)庫
簡單的想一下规脸,好像這種方式不錯,就算是第一步刪除緩存成功熊咽,第二步寫數(shù)據(jù)庫失敗莫鸭,則只會引發(fā)一次Cache Miss,對數(shù)據(jù)沒有影響横殴,其實(shí)仔細(xì)一想并發(fā)下也很容易導(dǎo)致了臟數(shù)據(jù)被因,例如
- 請求A進(jìn)行寫操作,刪除緩存
- 請求B查詢發(fā)現(xiàn)緩存不存在
- 請求B去數(shù)據(jù)庫查詢得到舊值
- 請求B將舊值寫入緩存
- 請求A將新值寫入數(shù)據(jù)庫
那怎么解決呢滥玷,不著急氏身,先看第四種情況,后面再統(tǒng)一說第三種和第四種的解決方案
先更新數(shù)據(jù)庫惑畴,再刪除緩存
這種方式也存在上面的問題蛋欣,但是出現(xiàn)的概率比上面那種方式低,例如:
- 請求緩存剛好失效
- 請求A查詢數(shù)據(jù)庫如贷,得一個舊值
- 請求B將新值寫入數(shù)據(jù)庫
- 請求B刪除緩存
- 請求A將查到的舊值寫入緩存
這樣就出現(xiàn)臟數(shù)據(jù)了陷虎,然而,實(shí)際上出現(xiàn)的概率可能非常低杠袱,因?yàn)檫@個條件需要發(fā)生在讀緩存時緩存失效尚猿,而且并發(fā)著有一個寫操作。而實(shí)際上數(shù)據(jù)庫的寫操作會比讀操作慢得多楣富,而且還要鎖表凿掂,而讀操作必需在寫操作前進(jìn)入數(shù)據(jù)庫操作,而又要晚于寫操作刪除緩存纹蝴,所有的這些條件都具備的概率基本并不大庄萎,但是還是會有出現(xiàn)的概率。
并且假如第一步寫數(shù)據(jù)庫成功塘安,第二步刪除緩存失敗糠涛,這樣也導(dǎo)致臟數(shù)據(jù)。
為了應(yīng)對這種問題兼犯,所以提出了延時雙刪策略忍捡。
延時雙刪策略
第三種方案并發(fā)問題解決方案:
- 先刪除(淘汰)緩存
- 再寫數(shù)據(jù)庫(這兩步和原來一樣)
- 休眠1秒集漾,再次刪除(淘汰)緩
第四種方案并發(fā)問題解決方案:
- 先寫數(shù)據(jù)庫
- 再刪除(淘汰)緩存(這兩步和原來一樣)
- 休眠1秒,再次刪除(淘汰)緩存
這個1秒應(yīng)該看你的業(yè)務(wù)場景砸脊,應(yīng)該自行評估自己的項目的讀數(shù)據(jù)業(yè)務(wù)邏輯的耗時具篇,然后寫數(shù)據(jù)的休眠時間則在讀數(shù)據(jù)業(yè)務(wù)邏輯的耗時基礎(chǔ)上,加幾百ms即可(以保證拿到舊數(shù)據(jù)的線程把數(shù)據(jù)保存到了 redis)凌埂,這么做確保讀請求結(jié)束栽连,寫請求可以刪除讀請求造成的緩存臟數(shù)據(jù)