Redis緩存雪崩、穿透、擊穿三步曲
本文收集了一些 Redis 使用中經(jīng)常遇到的一些問(wèn)題,和與之相對(duì)應(yīng)的解決方案,這些內(nèi)容不但會(huì)出現(xiàn)在實(shí)際工作中,也是面試的高頻問(wèn)題,接下來(lái)一起來(lái)看。
一、緩存雪崩
緩存雪崩是指在短時(shí)間內(nèi),有大量緩存同時(shí)過(guò)期,導(dǎo)致大量的請(qǐng)求直接查詢(xún)數(shù)據(jù)庫(kù),從而對(duì)數(shù)據(jù)庫(kù)造成了巨大的壓力,嚴(yán)重情況下可能會(huì)導(dǎo)致數(shù)據(jù)庫(kù)宕機(jī)的情況叫做緩存雪崩。
我們先來(lái)看下正常情況下和緩存雪崩時(shí)程序的執(zhí)行流程圖,正常情況下系統(tǒng)的執(zhí)行流程如下圖所示:
緩存雪崩的執(zhí)行流程,如下圖所示:
以上對(duì)比圖可以看出緩存雪崩對(duì)系統(tǒng)造成的影響,那如何解決緩存雪崩的問(wèn)題?
緩存雪崩的常用解決方案有以下幾個(gè)。
1.加鎖排隊(duì)
加鎖排隊(duì)可以起到緩沖的作用,防止大量的請(qǐng)求同時(shí)操作數(shù)據(jù)庫(kù),但它的缺點(diǎn)是增加了系統(tǒng)的響應(yīng)時(shí)間,降低了系統(tǒng)的吞吐量,犧牲了一部分用戶(hù)體驗(yàn)。
加鎖排隊(duì)的代碼實(shí)現(xiàn),如下所示:
// 緩存 key
String cacheKey = "userlist";
// 查詢(xún)緩存
String data = jedis.get(cacheKey);
if (StringUtils.isNotBlank(data)) {
// 查詢(xún)到數(shù)據(jù),直接返回結(jié)果
return data;
} else {
// 先排隊(duì)查詢(xún)數(shù)據(jù)庫(kù),在放入緩存
synchronized (cacheKey) {
data = jedis.get(cacheKey);
if (!StringUtils.isNotBlank(data)) { // 雙重判斷
// 查詢(xún)數(shù)據(jù)庫(kù)
data = findUserInfo();
// 放入緩存
jedis.set(cacheKey, data);
}
return data;
}
}
以上為加鎖排隊(duì)的實(shí)現(xiàn)示例,讀者可根據(jù)自己的實(shí)際項(xiàng)目情況做相應(yīng)的修改。
2.隨機(jī)化過(guò)期時(shí)間
為了避免緩存同時(shí)過(guò)期,可在設(shè)置緩存時(shí)添加隨機(jī)時(shí)間,這樣就可以極大的避免大量的緩存同時(shí)失效。
示例代碼如下:
// 緩存原本的失效時(shí)間
int exTime = 10 * 60;
// 隨機(jī)數(shù)生成類(lèi)
Random random = new Random();
// 緩存設(shè)置
jedis.setex(cacheKey, exTime+random.nextInt(1000) , value);
3.設(shè)置二級(jí)緩存
二級(jí)緩存指的是除了 Redis 本身的緩存,再設(shè)置一層緩存,當(dāng) Redis 失效之后,先去查詢(xún)二級(jí)緩存。
例如可以設(shè)置一個(gè)本地緩存,在 Redis 緩存失效的時(shí)候先去查詢(xún)本地緩存而非查詢(xún)數(shù)據(jù)庫(kù)。
加入二級(jí)緩存之后程序執(zhí)行流程,如下圖所示:
二、緩存穿透
緩存穿透是指查詢(xún)數(shù)據(jù)庫(kù)和緩存都無(wú)數(shù)據(jù),因?yàn)閿?shù)據(jù)庫(kù)查詢(xún)無(wú)數(shù)據(jù),出于容錯(cuò)考慮,不會(huì)將結(jié)果保存到緩存中,因此每次請(qǐng)求都會(huì)去查詢(xún)數(shù)據(jù)庫(kù),這種情況就叫做緩存穿透。
緩存穿透執(zhí)行流程如下圖所示:
其中紅色路徑表示緩存穿透的執(zhí)行路徑,可以看出緩存穿透會(huì)給數(shù)據(jù)庫(kù)造成很大的壓力。
緩存穿透的解決方案有以下幾個(gè)。
1.使用過(guò)濾器
我們可以使用過(guò)濾器來(lái)減少對(duì)數(shù)據(jù)庫(kù)的請(qǐng)求,例如使用我們前面章節(jié)所學(xué)的布隆過(guò)濾器,我們這里簡(jiǎn)單復(fù)習(xí)一下布隆過(guò)濾器,它的原理是將數(shù)據(jù)庫(kù)的數(shù)據(jù)哈希到 bitmap 中,每次查詢(xún)之前,先使用布隆過(guò)濾器過(guò)濾掉一定不存在的無(wú)效請(qǐng)求,從而避免了無(wú)效請(qǐng)求給數(shù)據(jù)庫(kù)帶來(lái)的查詢(xún)壓力。
2.緩存空結(jié)果
另一種方式是我們可以把每次從數(shù)據(jù)庫(kù)查詢(xún)的數(shù)據(jù)都保存到緩存中,為了提高前臺(tái)用戶(hù)的使用體驗(yàn) (解決長(zhǎng)時(shí)間內(nèi)查詢(xún)不到任何信息的情況),我們可以將空結(jié)果的緩存時(shí)間設(shè)置得短一些,例如 3~5 分鐘。
三、緩存擊穿
緩存擊穿指的是某個(gè)熱點(diǎn)緩存,在某一時(shí)刻恰好失效了,然后此時(shí)剛好有大量的并發(fā)請(qǐng)求,此時(shí)這些請(qǐng)求將會(huì)給數(shù)據(jù)庫(kù)造成巨大的壓力,這種情況就叫做緩存擊穿。
緩存擊穿的執(zhí)行流程如下圖所示:
它的解決方案有以下幾個(gè)。
1.加鎖排隊(duì)
此處理方式和緩存雪崩加鎖排隊(duì)的方法類(lèi)似,都是在查詢(xún)數(shù)據(jù)庫(kù)時(shí)加鎖排隊(duì),緩沖操作請(qǐng)求以此來(lái)減少服務(wù)器的運(yùn)行壓力。
2.設(shè)置永不過(guò)期
對(duì)于某些熱點(diǎn)緩存,我們可以設(shè)置永不過(guò)期,這樣就能保證緩存的穩(wěn)定性,但需要注意在數(shù)據(jù)更改之后,要及時(shí)更新此熱點(diǎn)緩存,不然就會(huì)造成查詢(xún)結(jié)果的誤差。
3.緩存預(yù)熱
首先來(lái)說(shuō),緩存預(yù)熱并不是一個(gè)問(wèn)題,而是使用緩存時(shí)的一個(gè)優(yōu)化方案,它可以提高前臺(tái)用戶(hù)的使用體驗(yàn)。
緩存預(yù)熱指的是在系統(tǒng)啟動(dòng)的時(shí)候,先把查詢(xún)結(jié)果預(yù)存到緩存中,以便用戶(hù)后面查詢(xún)時(shí)可以直接從緩存中讀取,以節(jié)約用戶(hù)的等待時(shí)間。
緩存預(yù)熱的執(zhí)行流程,如下圖所示:
緩存預(yù)熱的實(shí)現(xiàn)思路有以下三種:
- 把需要緩存的方法寫(xiě)在系統(tǒng)初始化的方法中,這樣系統(tǒng)在啟動(dòng)的時(shí)候就會(huì)自動(dòng)的加載數(shù)據(jù)并緩存數(shù)據(jù);
- 把需要緩存的方法掛載到某個(gè)頁(yè)面或后端接口上,手動(dòng)觸發(fā)緩存預(yù)熱;
- 設(shè)置定時(shí)任務(wù),定時(shí)自動(dòng)進(jìn)行緩存預(yù)熱。
小結(jié)
本文介紹了緩存雪崩產(chǎn)生的原因是因?yàn)槎虝r(shí)間內(nèi)大量緩存同時(shí)失效,而導(dǎo)致大量請(qǐng)求直接查詢(xún)數(shù)據(jù)庫(kù)的情況,解決方案是加鎖、隨機(jī)設(shè)置過(guò)期時(shí)間和設(shè)置二級(jí)緩存等。
還介紹了查詢(xún)數(shù)據(jù)庫(kù)無(wú)數(shù)據(jù)時(shí)會(huì)導(dǎo)致的每次空查詢(xún)都不走緩存的緩存穿透問(wèn)題,解決方案是使用布隆過(guò)濾器和緩存空結(jié)果等。
同時(shí)還介紹了緩存在某一個(gè)高并發(fā)時(shí)刻突然失效導(dǎo)致的緩存擊穿問(wèn)題,以及解決方案——加鎖、設(shè)置永不過(guò)期等方案,最后還介紹了優(yōu)化系統(tǒng)性能的手段緩存預(yù)熱。