為了減少db的讀壓力,加快讀速度,系統(tǒng)使用cache做緩存史飞,會(huì)引起cache一致性問題冬竟。因?yàn)閐b會(huì)有事務(wù)性導(dǎo)致回滾,而cache無法回滾杠愧,會(huì)導(dǎo)致臟數(shù)據(jù)。
一般情況下,我們會(huì)在保存數(shù)據(jù)時(shí)纹安,先穿透保存到DB中,再同步數(shù)據(jù)到redis中。
為了保證存儲(chǔ)層對(duì)外層透明厢岂,我們會(huì)把DB與redis操作封裝光督,對(duì)上層調(diào)用來說完全透明,不關(guān)心數(shù)據(jù)具體如何存儲(chǔ)塔粒。
例如在我們的實(shí)際業(yè)務(wù)中有如下場(chǎng)景:A表插入一條數(shù)據(jù)结借,同步到redis中,B表插入一條數(shù)據(jù)卒茬,同步到redis中船老。如果B表插入數(shù)據(jù)失敗,事務(wù)回滾扬虚,A表中數(shù)據(jù)可以回滾努隙,但是redis無法回滾。會(huì)導(dǎo)致redis中有臟數(shù)據(jù)辜昵。
facebook的一篇論文給出如下設(shè)計(jì):
查詢:先查詢cache荸镊,miss時(shí)查詢db,寫入cache
寫:寫db成功后堪置,失效cache
重點(diǎn)說下寫:如果寫db成功后躬存,寫cache,會(huì)有事務(wù)性和并發(fā)性兩方面問題舀锨。
1.事務(wù)性問題:一個(gè)事務(wù)包含多個(gè)db操作岭洲,操作一些db成功,寫cache成功坎匿,操作二寫db失敗盾剩,事務(wù)回滾,db數(shù)據(jù)回滾替蔬,cache無法回滾告私,導(dǎo)致臟數(shù)據(jù)。
2.并發(fā)性問題:兩個(gè)更新操作并發(fā)承桥,如更新名字驻粟,并且cache中key以名字為關(guān)鍵字,更新一寫db成功凶异,寫緩存XXXX_name1成功蜀撑。更新二寫db成功,寫緩存XXXX_name2成功剩彬。導(dǎo)致cache臟數(shù)據(jù)酷麦。
這里再說一下一般更新操作順序是失效cache,寫db喉恋,寫cache贴铜。會(huì)有并發(fā)問題粪摘。
兩個(gè)并發(fā)操作,更新和讀绍坝,左邊寫線程,右邊為讀線程
①更新操作刪除cache
②讀操作讀cache苔悦,miss
③讀db轩褐,此時(shí)是舊數(shù)據(jù)
④寫db,寫cache
⑤寫cache 導(dǎo)致cache中臟數(shù)據(jù)玖详。
雖然寫db成功后把介,失效cache也會(huì)有并發(fā)問題:更新和讀并發(fā)
①查詢cache
②寫db,失效cache
③寫chache
導(dǎo)致cache中臟數(shù)據(jù)蟋座,但是概率極低拗踢,并且一般db中寫時(shí)間長于讀時(shí)間,并且寫會(huì)鎖表向臀,讀需要在寫前進(jìn)入巢墅,并且要晚于寫操作更新緩存,所以發(fā)生概率極低券膀。
解決方法是 2PC或是Paxos協(xié)議君纫,代價(jià)較大。
所以我們采用的方式是:
寫數(shù)據(jù)只寫db
更新數(shù)據(jù)先更新db芹彬,再失效cache
讀數(shù)據(jù)蓄髓,先讀cache,未命中讀db舒帮,寫入cache