緩存失效了,怎么辦?
Part 01
前言
在高并發(fā)的系統(tǒng)架構(gòu)中,大量網(wǎng)絡(luò)請(qǐng)求的并發(fā)處理,導(dǎo)致數(shù)據(jù)庫(kù)的I/O消耗是非常巨大的,為了快速讀取數(shù)據(jù),減少網(wǎng)絡(luò)請(qǐng)求時(shí)延,緩解數(shù)據(jù)庫(kù)的壓力,因此在軟件開發(fā)中引入了緩存技術(shù)。但是在緩存的使用過(guò)程中也會(huì)遇到一些特殊情況導(dǎo)致緩存失效,常見的緩存失效的情況有三種:緩存穿透、緩存擊穿、緩存雪崩。
Part 02
緩存失效的三種情況
緩存(以Redis緩存為例)的引入可以減少請(qǐng)求數(shù)據(jù)庫(kù)的次數(shù),提高查詢效率,從而提升系統(tǒng)性能。一般的流程是:應(yīng)用發(fā)起請(qǐng)求后,先查詢緩存中是否存在所需數(shù)據(jù),如果緩存中存在,直接返回?cái)?shù)據(jù),如果緩存中不存在所需數(shù)據(jù),則需要去查詢數(shù)據(jù)庫(kù),如果數(shù)據(jù)庫(kù)中存在所需數(shù)據(jù),則一方面存入緩存,另一方面返回查詢結(jié)果,如果數(shù)據(jù)庫(kù)中不存在,則返回空或者錯(cuò)誤。
圖1 緩存使用流程圖
- 緩存穿透
緩存穿透(Cache Penetration)是指查詢一個(gè)一定不存在的數(shù)據(jù),即用戶訪問(wèn)的數(shù)據(jù)既不在緩存當(dāng)中,也不在數(shù)據(jù)庫(kù)中。由于緩存中查詢不到數(shù)據(jù),請(qǐng)求會(huì)去查詢數(shù)據(jù)庫(kù),然而數(shù)據(jù)庫(kù)中也不存在該數(shù)據(jù),也不會(huì)寫入緩存,導(dǎo)致查詢?cè)摂?shù)據(jù)的時(shí)候,每次都要去數(shù)據(jù)庫(kù)中查詢,給數(shù)據(jù)庫(kù)到來(lái)壓力。
- 緩存雪崩
緩存雪崩(Cache Avalanche)是指大量的緩存數(shù)據(jù)在某一時(shí)刻超過(guò)了緩存的過(guò)期時(shí)間,同時(shí)失效,導(dǎo)致高并發(fā)的請(qǐng)求同時(shí)去訪問(wèn)數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)壓力過(guò)大,導(dǎo)致系統(tǒng)崩潰。這是針對(duì)多個(gè)緩存數(shù)據(jù)而言的。
- 緩存擊穿
緩存擊穿(Cache Breakdown)是指緩存過(guò)期的一瞬間,有大量的請(qǐng)求去查詢同一個(gè)緩存數(shù)據(jù),由于該數(shù)據(jù)在承載著大并發(fā),當(dāng)該數(shù)據(jù)失效的一瞬間,持續(xù)的大并發(fā)就會(huì)直接去請(qǐng)求數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)壓力倍增。這是針對(duì)一個(gè)緩存數(shù)據(jù)而言的。
Part 03
解決方案
針對(duì)緩存穿透的解決方案:
1.第一種方案是采用布隆過(guò)濾器進(jìn)行數(shù)據(jù)攔截。這也是針對(duì)緩存擊穿采用的常用方案。在寫入數(shù)據(jù)時(shí),使用布隆過(guò)濾器對(duì)數(shù)據(jù)的key進(jìn)行標(biāo)記,當(dāng)帶有數(shù)據(jù)key的請(qǐng)求過(guò)來(lái)后,先用布隆過(guò)濾器驗(yàn)證key是否存在,如果存在,再進(jìn)入緩存或者數(shù)據(jù)庫(kù)中進(jìn)行查詢。
2.第二種方案是緩存空值。當(dāng)在數(shù)據(jù)庫(kù)中查詢不到數(shù)據(jù)時(shí),將其緩存為空值或者默認(rèn)值。此時(shí)需要注意,針對(duì)其的緩存過(guò)期時(shí)間不宜過(guò)長(zhǎng),一般設(shè)置為5分鐘內(nèi),當(dāng)數(shù)據(jù)庫(kù)被寫入或者更新該key的新數(shù)據(jù)時(shí),緩存必須同時(shí)更新,保證數(shù)據(jù)的一致性。
針對(duì)緩存雪崩的解決方案:
1.一般是將key的過(guò)期時(shí)間后面增加一個(gè)隨機(jī)數(shù),讓過(guò)期時(shí)間分散開,使key均勻失效,減少緩存時(shí)間過(guò)期的重復(fù)率。
2.利用加鎖或隊(duì)列的方式,保證緩存單線程寫,但是這種方案會(huì)影響并發(fā)量,多個(gè)請(qǐng)求過(guò)來(lái)時(shí),只有一個(gè)在進(jìn)行正常的操作,其他請(qǐng)求都會(huì)在等待的狀態(tài),影響程序性能,不推薦使用。
3.使用緩存標(biāo)記,這是比較好的解決辦法。判斷標(biāo)記是否過(guò)期,過(guò)期則去數(shù)據(jù)庫(kù)中請(qǐng)求,而緩存數(shù)據(jù)的過(guò)期時(shí)間要設(shè)置的比緩存標(biāo)記長(zhǎng)些,如此一來(lái),當(dāng)一個(gè)請(qǐng)求去操作數(shù)據(jù)庫(kù)的時(shí)候,其他的請(qǐng)求拿到的是上一次的緩存數(shù)據(jù)。
針對(duì)緩存擊穿的解決方案:
1.使用互斥鎖,當(dāng)緩存的key過(guò)期時(shí),多個(gè)請(qǐng)求過(guò)來(lái)時(shí)只允許一個(gè)請(qǐng)求去查詢數(shù)據(jù)庫(kù)構(gòu)建緩存,其他請(qǐng)求等待該請(qǐng)求執(zhí)行完畢之后,重新從緩存中獲取數(shù)據(jù)。
2.針對(duì)訪問(wèn)量比較大的數(shù)據(jù),即熱點(diǎn)數(shù)據(jù),不設(shè)置緩存過(guò)期時(shí)間,后臺(tái)異步更新緩存,適用于不嚴(yán)格要求緩存一致性的場(chǎng)景。