一、緩存雪崩
1)、什么是緩存雪崩?
如果緩存集中在一段時(shí)間內(nèi)失效,發(fā)生大量的緩存穿透,所有的查詢都落在數(shù)據(jù)庫(kù)上,造成了緩存雪崩
由于原有緩存失效,新緩存未到期間所有原本應(yīng)該訪問(wèn)緩存的請(qǐng)求都去查詢數(shù)據(jù)庫(kù)了,而對(duì)數(shù)據(jù)庫(kù)CPU和內(nèi)存造成巨大壓力,嚴(yán)重的會(huì)造成數(shù)據(jù)庫(kù)宕機(jī)
2)、有什么解決方案來(lái)防止緩存雪崩?
1)加鎖排隊(duì)
mutex互斥鎖解決,Redis的SETNX去set一個(gè)mutex key,當(dāng)操作返回成功時(shí),再進(jìn)行加載數(shù)據(jù)庫(kù)的操作并回設(shè)緩存,否則,就重試整個(gè)get緩存的方法
2)數(shù)據(jù)預(yù)熱
緩存預(yù)熱就是系統(tǒng)上線后,將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)。這樣就可以避免在用戶請(qǐng)求的時(shí)候,先查詢數(shù)據(jù)庫(kù),然后再將數(shù)據(jù)緩存的問(wèn)題。用戶直接查詢事先被預(yù)熱的緩存數(shù)據(jù)。可以通過(guò)緩存reload機(jī)制,預(yù)先去更新緩存,再即將發(fā)生大并發(fā)訪問(wèn)前手動(dòng)觸發(fā)加載緩存不同的key
3)雙層緩存策略
C1為原始緩存,C2為拷貝緩存,C1失效時(shí),可以訪問(wèn)C2,C1緩存失效時(shí)間設(shè)置為短期,C2設(shè)置為長(zhǎng)期
4)定時(shí)更新緩存策略
實(shí)效性要求不高的緩存,容器啟動(dòng)初始化加載,采用定時(shí)任務(wù)更新或移除緩存
5)設(shè)置不同的過(guò)期時(shí)間,讓緩存失效的時(shí)間點(diǎn)盡量均勻
二、緩存擊穿
1)、什么是緩存擊穿?
在平常高并發(fā)的系統(tǒng)中,大量的請(qǐng)求同時(shí)查詢一個(gè)key時(shí),此時(shí)這個(gè)key正好失效了,就會(huì)導(dǎo)致大量的請(qǐng)求都打到數(shù)據(jù)庫(kù)上面去。這種現(xiàn)象我們稱為緩存擊穿
2)、會(huì)帶來(lái)什么問(wèn)題
會(huì)造成某一時(shí)刻數(shù)據(jù)庫(kù)請(qǐng)求量過(guò)大,壓力劇增
3)、如何解決
上面的現(xiàn)象是多個(gè)線程同時(shí)去查詢數(shù)據(jù)庫(kù)的這條數(shù)據(jù),那么我們可以在第一個(gè)查詢數(shù)據(jù)的請(qǐng)求上使用一個(gè)互斥鎖來(lái)鎖住它
其他的線程走到這一步拿不到鎖就等著,等第一個(gè)線程查詢到了數(shù)據(jù),然后做緩存。后面的線程進(jìn)來(lái)發(fā)現(xiàn)已經(jīng)有緩存了,就直接走緩存
三、緩存穿透
1)、什么是緩存穿透?
緩存穿透是指用戶查詢數(shù)據(jù),在數(shù)據(jù)庫(kù)沒(méi)有,自然在緩存中也不會(huì)有。這樣就導(dǎo)致用戶查詢的時(shí)候,在緩存中找不到對(duì)應(yīng)key的value,每次都要去數(shù)據(jù)庫(kù)再查詢一遍,然后返回空(相當(dāng)于進(jìn)行了兩次無(wú)用的查詢)。這樣請(qǐng)求就繞過(guò)緩存直接查數(shù)據(jù)庫(kù)
2)、有什么解決方案來(lái)防止緩存穿透?
1)緩存空值
如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障)我們?nèi)匀话堰@個(gè)空結(jié)果進(jìn)行緩存,但它的過(guò)期時(shí)間會(huì)很短,最長(zhǎng)不超過(guò)5分鐘。通過(guò)這個(gè)設(shè)置的默認(rèn)值存放到緩存,這樣第二次到緩存中獲取就有值了,而不會(huì)繼續(xù)訪問(wèn)數(shù)據(jù)庫(kù)
2)采用布隆過(guò)濾器BloomFilter
優(yōu)勢(shì):占用內(nèi)存空間很小,位存儲(chǔ);性能特別高,使用key的hash判斷key存不存在
將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中,一個(gè)一定不存在的數(shù)據(jù)會(huì)被這個(gè)bitmap攔截掉,從而避免了對(duì)底層存儲(chǔ)系統(tǒng)的查詢壓力
在緩存之前在加一層BloomFilter,在查詢的時(shí)候先去BloomFilter去查詢key是否存在,如果不存在就直接返回,存在再去查詢緩存,緩存中沒(méi)有再去查詢數(shù)據(jù)庫(kù)
四、常見(jiàn)的幾種緩存模式
1)、Cache Aside
應(yīng)用在查詢數(shù)據(jù)的時(shí)候,先從緩存Cache中讀取數(shù)據(jù),如果緩存中沒(méi)有,則再?gòu)臄?shù)據(jù)庫(kù)中讀取數(shù)據(jù),得到數(shù)據(jù)庫(kù)的數(shù)據(jù)之后,將這個(gè)數(shù)據(jù)也放到緩存Cache中
如果應(yīng)用要更新某個(gè)數(shù)據(jù),也是先去更新數(shù)據(jù)庫(kù)中的數(shù)據(jù),更新完成之后,則通過(guò)指令讓緩存Cache中的數(shù)據(jù)失效
1)這里為什么不讓更新操作在寫完數(shù)據(jù)庫(kù)之后,緊接著去把緩存Cache中的數(shù)據(jù)也修改了呢?
主要是因?yàn)檫@樣做的話,就有2個(gè)寫操作的事件了,擔(dān)心在并發(fā)的情況下會(huì)導(dǎo)致臟數(shù)據(jù),舉個(gè)例子:假如同時(shí)有2個(gè)請(qǐng)求,請(qǐng)求A和請(qǐng)求B,并發(fā)的執(zhí)行。請(qǐng)求A是要去讀數(shù)據(jù),請(qǐng)求B是要去更新數(shù)據(jù)。初始狀態(tài)緩存中是沒(méi)有數(shù)據(jù)的,當(dāng)請(qǐng)求A讀到數(shù)據(jù)之后,準(zhǔn)備往回寫的時(shí)候,此刻,請(qǐng)求B正好要更新數(shù)據(jù),更新完了數(shù)據(jù)庫(kù)之后,又去把緩存更新了,那請(qǐng)求A再往緩存中寫的就是舊數(shù)據(jù)了,屬于臟數(shù)據(jù)
2)那么Cache Aside模式就沒(méi)有臟數(shù)據(jù)問(wèn)題了嗎?
在極端情況下也可能會(huì)產(chǎn)生臟數(shù)據(jù)。例如,同時(shí)有2個(gè)請(qǐng)求,請(qǐng)求A和請(qǐng)求B,并發(fā)的執(zhí)行。請(qǐng)求A是要去讀數(shù)據(jù),請(qǐng)求B是要去寫數(shù)據(jù)。假如初始狀態(tài)緩存中沒(méi)有這個(gè)數(shù)據(jù),那請(qǐng)求A發(fā)現(xiàn)緩存中沒(méi)有數(shù)據(jù),就會(huì)去數(shù)據(jù)庫(kù)中讀數(shù)據(jù),讀到了數(shù)據(jù)準(zhǔn)備寫回緩存中,就在這個(gè)時(shí)候,請(qǐng)求B是要去寫數(shù)據(jù)的,請(qǐng)求B在寫完數(shù)據(jù)庫(kù)的數(shù)據(jù)之后,又去設(shè)置了緩存失效。這個(gè)時(shí)候,請(qǐng)求A由于在數(shù)據(jù)庫(kù)中讀到了之前的舊數(shù)據(jù),開(kāi)始往緩存中寫數(shù)據(jù)了,此時(shí)寫進(jìn)入的就也是舊數(shù)據(jù)。那么最終就會(huì)導(dǎo)致,緩存中的數(shù)據(jù)與數(shù)據(jù)庫(kù)的數(shù)據(jù)不一致,造成了臟數(shù)據(jù)
2)、Read/Write Through
應(yīng)用要讀數(shù)據(jù)和更新數(shù)據(jù)都直接訪問(wèn)緩存服務(wù)
緩存服務(wù)同步地將數(shù)據(jù)更新到數(shù)據(jù)庫(kù)
出現(xiàn)臟數(shù)據(jù)的概率較低,但是就強(qiáng)依賴緩存,對(duì)緩存服務(wù)的穩(wěn)定性有較大要求
3)、Write Behind模式
應(yīng)用要讀數(shù)據(jù)和更新數(shù)據(jù)都直接訪問(wèn)緩存服務(wù)
緩存服務(wù)異步地將數(shù)據(jù)更新到數(shù)據(jù)庫(kù)(通過(guò)異步任務(wù))
速度快,效率會(huì)非常高,但是數(shù)據(jù)的一致性比較差,還可能會(huì)有數(shù)據(jù)的丟失情況,實(shí)現(xiàn)邏輯也較為復(fù)雜 。
聯(lián)系客服