[TOC]
參考
究竟先操作緩存竞思,還是數(shù)據(jù)庫(kù)雳殊?
1. 概要
緩存服務(wù)和數(shù)據(jù)服務(wù)(如數(shù)據(jù)庫(kù))是獨(dú)立的系統(tǒng)搓扯,更新的時(shí)候,無法做到原子性地操作兩個(gè)服務(wù)包归,即有兩步操作來更新緩存服務(wù)和數(shù)據(jù)服務(wù)的數(shù)據(jù)锨推。因此在并發(fā)讀寫以及第二步操作異常時(shí),會(huì)出現(xiàn)各種問題箫踩。
此處說的第二步操作爱态,要么是操作緩存服務(wù),要么是操作數(shù)據(jù)服務(wù)境钟,根據(jù)具體的實(shí)現(xiàn)方式而定锦担。
2. 緩存使用的誤區(qū)
服務(wù)與服務(wù)之間不要通過緩存?zhèn)鬟f數(shù)據(jù)
如果緩存掛掉,可能導(dǎo)致雪崩慨削,此時(shí)要做高可用緩存洞渔,或者水平切分
調(diào)用方不宜再單獨(dú)使用緩存存儲(chǔ)服務(wù)底層的數(shù)據(jù)套媚,容易出現(xiàn)數(shù)據(jù)不一致,以及反向依賴
不同服務(wù)磁椒,緩存實(shí)例要做垂直拆分
3. 常見更新模式
3.1. cache aside
同時(shí)更新緩存和數(shù)據(jù)庫(kù)
這是最常用的pattern了堤瘤。其具體邏輯如下:
- 數(shù)據(jù)查詢:應(yīng)用程序先從cache取數(shù)據(jù),沒有得到浆熔,則從數(shù)據(jù)庫(kù)中取數(shù)據(jù)本辐,成功后,放到緩存中医增。
- 數(shù)據(jù)更新:先把數(shù)據(jù)存到數(shù)據(jù)庫(kù)中慎皱,成功后,再讓緩存失效叶骨。
此處茫多,更新的時(shí)候是先數(shù)據(jù)庫(kù),后緩存忽刽,也有另一種思路天揖,先緩存,后數(shù)據(jù)庫(kù)跪帝。兩種思路詳見后文分析今膊。
3.1.1. 先數(shù)據(jù)庫(kù)后緩存
這是最常用最常用的pattern。
分析異常情況如下:
-
:
- 一個(gè)是查詢請(qǐng)求歉甚,一個(gè)是更新請(qǐng)求的并發(fā)万细。更新請(qǐng)求在寫完數(shù)據(jù)庫(kù)之后扑眉,讓緩存失效之前纸泄,查詢請(qǐng)求到來,此時(shí)腰素,緩存依然有效聘裁,所以,并發(fā)的查詢請(qǐng)求拿的是沒有更新的數(shù)據(jù)弓千,但是衡便,隨后的更新請(qǐng)求會(huì)馬上讓緩存的失效了,在這之后的查詢請(qǐng)求再把數(shù)據(jù)從數(shù)據(jù)庫(kù)中取出來洋访,放入到緩存镣陕。
- 同樣是一個(gè)查詢請(qǐng)求姻政,一個(gè)更新請(qǐng)求的并發(fā)呆抑。查詢請(qǐng)求 cache miss,讀了數(shù)據(jù)庫(kù)汁展,在將數(shù)據(jù)放到緩存之前鹊碍,更新請(qǐng)求到來厌殉,更新了數(shù)據(jù)庫(kù)內(nèi)容且讓緩存失效,此時(shí)查詢請(qǐng)求再將之前讀的老數(shù)據(jù)放入到緩存侈咕。這樣也會(huì)導(dǎo)致緩存和數(shù)據(jù)不一致公罕。 為了避免這種情況楼眷,可以采取延遲失效,同時(shí)讀請(qǐng)求回填 cache 的時(shí)候熊尉,如果遇到 key 存在摩桶,則不更新,只能等待超時(shí)失效之后帽揪,讀請(qǐng)求回填的 cache 才可以更新硝清;也可以考慮延遲雙刪,緩存失效后转晰,等待1s 再失效一次緩存芦拿。
- :如果是更新請(qǐng)求在寫完數(shù)據(jù)庫(kù),讓緩存失效之前查邢,更新請(qǐng)求發(fā)起方異常(如宕機(jī))蔗崎,此時(shí)數(shù)據(jù)庫(kù)已經(jīng)更新了,但是緩存一直沒有更新扰藕。導(dǎo)致查詢請(qǐng)求從緩存獲取的數(shù)據(jù)一直都是老數(shù)據(jù)缓苛。為了規(guī)避第二步操作異常,有兩種方法:1. 考慮在緩存上加上失效時(shí)間邓深,但是這和周期性的更新緩存類似未桥,還是會(huì)對(duì)系統(tǒng)性能還是有較大的影響;2. 使用可靠的消息隊(duì)列記錄(如 binlog)更改芥备,然后消費(fèi)消息進(jìn)行緩存失效
這是標(biāo)準(zhǔn)的design pattern冬耿,包括Facebook的論文《Scaling Memcache at Facebook》也使用了這個(gè)策略。
3.1.2. 先緩存后數(shù)據(jù)庫(kù)
分析如下:
:一個(gè)是查詢請(qǐng)求萌壳,一個(gè)是更新請(qǐng)求的并發(fā)亦镶。在更新請(qǐng)求讓緩存失效之后,寫數(shù)據(jù)庫(kù)之前袱瓮,查詢操作到來缤骨,此時(shí),會(huì)從數(shù)據(jù)庫(kù)讀到老數(shù)據(jù)進(jìn)而寫回到緩存尺借,等更新請(qǐng)求寫完數(shù)據(jù)庫(kù)绊起,
:如果是更新請(qǐng)求將緩存失效后褐望,在寫數(shù)據(jù)庫(kù)前勒庄,更新操作發(fā)起方異常(如宕機(jī))串前,此時(shí)僅僅是 cache miss,
針對(duì)第二種方案荡碾,我們可以采取以下兩種方案規(guī)避并發(fā)讀寫的問題:
- 在使緩存失效的時(shí)候,不是立即失效局装,而是采取延遲失效(比如說設(shè)置緩存失效時(shí)間為1s)坛吁,保證在數(shù)據(jù)庫(kù)更新之前,讀請(qǐng)求依舊能夠命中cache铐尚,進(jìn)而不會(huì)出現(xiàn)讀請(qǐng)求更新 cache 的情況拨脉。這個(gè)方案應(yīng)該是最佳方案。
- 延遲雙刪宣增。第一次緩存失效且數(shù)據(jù)庫(kù)操作完成之后玫膀,延遲再失效一次緩存。
3.1.3. 更新緩存還是失效緩存
為什么不是更新緩存爹脾,而是失效緩存帖旨?你可以看一下Quora上的這個(gè)問答《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?》,主要是
3.1.3. 結(jié)論
個(gè)人覺得灵妨,先緩存后數(shù)據(jù)庫(kù)解阅,配合延遲失效的方案最好。歡迎讀者討論泌霍。
3.2. Read/Write Through Pattern
先更新緩存货抄,緩存負(fù)責(zé)同步更新數(shù)據(jù)庫(kù)。
在上面的 Cache Aside 更新模式中朱转,應(yīng)用代碼需要維護(hù)兩個(gè)數(shù)據(jù)存儲(chǔ)蟹地,一個(gè)是緩存(Cache),一個(gè)是數(shù)據(jù)庫(kù)(Repository)肋拔。而在Read/Write Through 更新模式中锈津,應(yīng)用程序只需要維護(hù)緩存呀酸,數(shù)據(jù)庫(kù)的維護(hù)工作由緩存代理了凉蜂。
3.2.1. Read Through
Read Through 模式就是在查詢操作中更新緩存,也就是說性誉,當(dāng)緩存失效的時(shí)候窿吩,Cache Aside 模式是由調(diào)用方負(fù)責(zé)把數(shù)據(jù)加載入緩存,而 Read Through 則用緩存服務(wù)自己來加載错览。
3.2.2. Write Through
Write Through 模式和 Read Through 相仿纫雁,不過是在更新數(shù)據(jù)時(shí)發(fā)生。當(dāng)有數(shù)據(jù)更新的時(shí)候倾哺,如果沒有命中緩存轧邪,直接更新數(shù)據(jù)庫(kù)刽脖,然后返回。如果命中了緩存忌愚,則更新緩存曲管,然后由緩存自己更新數(shù)據(jù)庫(kù)(這是一個(gè)同步操作)。
3.3. Write Behind Caching Pattern
先更新緩存硕糊,緩存定時(shí)異步更新數(shù)據(jù)庫(kù)院水。
Write Behind Caching 更新模式就是在更新數(shù)據(jù)的時(shí)候,只更新緩存简十,不更新數(shù)據(jù)庫(kù)檬某,而我們的緩存會(huì)異步地批量更新數(shù)據(jù)庫(kù)。這個(gè)設(shè)計(jì)的好處就是直接操作內(nèi)存速度快螟蝙。因?yàn)楫惒交帜眨琖rite Behind Caching 更新模式還可以合并對(duì)同一個(gè)數(shù)據(jù)的多次操作到數(shù)據(jù)庫(kù),所以性能的提高是相當(dāng)可觀的胰默。
但其帶來的問題是厅瞎,數(shù)據(jù)不是強(qiáng)一致性的,而且可能會(huì)丟失初坠。另外和簸,Write Behind Caching 更新模式實(shí)現(xiàn)邏輯比較復(fù)雜,因?yàn)樗枰_認(rèn)有哪些數(shù)據(jù)是被更新了的碟刺,哪些數(shù)據(jù)需要刷到持久層上锁保。只有在緩存需要失效的時(shí)候,才會(huì)把它真正持久起來半沽。
3.4. 總結(jié)
三種緩存模式的優(yōu)缺點(diǎn):
- Cache Aside 更新模式實(shí)現(xiàn)起來比較簡(jiǎn)單爽柒,但是需要維護(hù)兩個(gè)數(shù)據(jù)存儲(chǔ),一個(gè)是緩存(Cache)者填,一個(gè)是數(shù)據(jù)庫(kù)(Repository)浩村。
- Read/Write Through 更新模式只需要維護(hù)一個(gè)數(shù)據(jù)存儲(chǔ)(緩存),但是實(shí)現(xiàn)起來要復(fù)雜一些占哟。
- Write Behind Caching 更新模式和Read/Write Through 更新模式類似心墅,區(qū)別是Write Behind Caching 更新模式的數(shù)據(jù)持久化操作是異步的,但是Read/Write Through 更新模式的數(shù)據(jù)持久化操作是同步的榨乎。優(yōu)點(diǎn)是直接操作內(nèi)存速度快怎燥,多次操作可以合并持久化到數(shù)據(jù)庫(kù)。缺點(diǎn)是數(shù)據(jù)可能會(huì)丟失蜜暑,例如系統(tǒng)斷電等铐姚。
緩存是通過犧牲強(qiáng)一致性來提高性能的。所以使用緩存提升性能肛捍,就是會(huì)有數(shù)據(jù)更新的延遲隐绵。這需要我們?cè)谠O(shè)計(jì)時(shí)結(jié)合業(yè)務(wù)仔細(xì)思考是否適合用緩存之众。然后緩存一定要設(shè)置過期時(shí)間,這個(gè)時(shí)間太短太長(zhǎng)都不好依许,太短的話請(qǐng)求可能會(huì)比較多的落到數(shù)據(jù)庫(kù)上酝枢,這也意味著失去了緩存的優(yōu)勢(shì)。太長(zhǎng)的話緩存中的臟數(shù)據(jù)會(huì)使系統(tǒng)長(zhǎng)時(shí)間處于一個(gè)延遲的狀態(tài)悍手,而且系統(tǒng)中長(zhǎng)時(shí)間沒有人訪問的數(shù)據(jù)一直存在內(nèi)存中不過期帘睦,浪費(fèi)內(nèi)存。