導(dǎo)致數(shù)據(jù)不一致性的場(chǎng)景:
場(chǎng)景一(查詢):
高并發(fā)的時(shí)候,線程A redis未命中,去查詢db,得到值1,還未回種redis,這時(shí)候db修改了,線程B redis未命中,查詢db,得到值2,但線程B先存入redis,然后線程A存入redis,這時(shí)候redis的數(shù)據(jù)是值1,是條臟數(shù)據(jù)。
解決方案:
通過(guò)加鎖解決,保證查詢db和存入redis操作的原子性,或者用樂(lè)觀鎖,加個(gè)版本號(hào)或者時(shí)間戳,存入redis之前查下,但還是要保證查和存的原子性
場(chǎng)景二(更新):
先寫入db,在刪除緩存,可能出現(xiàn)db已經(jīng)更新,但redis中未更新的情況,這時(shí)候redis命中后查到的數(shù)據(jù)就是舊數(shù)據(jù),所以不行。
場(chǎng)景三(更新):
先刪除緩存,再寫入db。這其實(shí)也有并發(fā)問(wèn)題:線程A是更新操作,先刪除緩存,但還沒(méi)寫入db,這時(shí)候線程B來(lái)了,是個(gè)查詢操作,發(fā)現(xiàn)緩存中沒(méi)有數(shù)據(jù),就去查db,但這時(shí)候線程A的寫入操作還沒(méi)完成,于是線程B查到了臟數(shù)據(jù)。
解決方案:
老外提出了一個(gè)緩存更新套路,名為Cache-Aside pattern。其中就指出
失效:應(yīng)用程序先從cache取數(shù)據(jù),沒(méi)有得到,則從數(shù)據(jù)庫(kù)中取數(shù)據(jù),成功后,放到緩存中。
命中:應(yīng)用程序從cache中取數(shù)據(jù),取到后返回。
更新:先把數(shù)據(jù)存到數(shù)據(jù)庫(kù)中,成功后,再讓緩存失效。
這樣的策略其實(shí)還是會(huì)出現(xiàn)并發(fā)問(wèn)題:
假設(shè)這會(huì)有兩個(gè)請(qǐng)求,一個(gè)請(qǐng)求A做查詢操作,一個(gè)請(qǐng)求B做更新操作,那么會(huì)有如下情形產(chǎn)生
(1)緩存剛好失效
(2)請(qǐng)求A查詢數(shù)據(jù)庫(kù),得一個(gè)舊值
(3)請(qǐng)求B將新值寫入數(shù)據(jù)庫(kù)
(4)請(qǐng)求B刪除緩存
(5)請(qǐng)求A將查到的舊值寫入緩存
此時(shí),產(chǎn)生臟數(shù)據(jù)的原因:
請(qǐng)求B的寫操作(3)要比請(qǐng)求A(2)的讀操作耗時(shí)更短,才會(huì)出現(xiàn)(4)先于(5)
但是出現(xiàn)該情況的可能性是有多大呢,這邊以讀寫分離為例,為啥會(huì)出現(xiàn)讀寫分離,讀寫分離的意義就是因?yàn)樽x操作比較快,耗資源少,不然要讀寫分離干啥?所以出現(xiàn)該場(chǎng)景的可能性太小了。
總結(jié)
redis和db的數(shù)據(jù)一致性理論上是不可能,如果真的對(duì)數(shù)據(jù)有強(qiáng)一致性的要求,就不應(yīng)該放緩存里!!