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