淺談Redis緩存擊穿、緩存穿透、緩存雪崩的解決方案
前言
在日常的項(xiàng)目中,緩存的使用場景是比較多的。緩存是分布式系統(tǒng)中的重要組件,主要解決在高并發(fā)、大數(shù)據(jù)場景下,熱點(diǎn)數(shù)據(jù)訪問的性能問題,提高性能的數(shù)據(jù)快速訪問。本文以Redis作為緩存時,針對常見的緩存擊穿、緩存穿透、緩存雪崩問題做簡單地說明,并且提供有效的解決方案。
Redis緩存使用場景
Redis會把數(shù)據(jù)庫中經(jīng)常被查詢的數(shù)據(jù)緩存起來,比如熱點(diǎn)數(shù)據(jù),這樣當(dāng)用戶通過網(wǎng)站或APP來訪問的時候,就不需要到數(shù)據(jù)庫中去查詢了,而是直接獲取 Redis中的緩存數(shù)據(jù),從而降低了后端數(shù)據(jù)庫的讀取壓力。如果說用戶查詢的數(shù)據(jù)Redis中沒有,此時用戶的查詢請求就會轉(zhuǎn)到數(shù)據(jù)庫,當(dāng)數(shù)據(jù)庫將數(shù)據(jù)返回給客戶端時,同時會將數(shù)據(jù)緩存到 Redis中,這樣用戶再次讀取時,就可以直接從Redis中獲取數(shù)據(jù)。流程圖如下所示:
Redis緩存穿透
緩存穿透是指用戶惡意的發(fā)起大量請求去查詢一個緩存(Redis)和數(shù)據(jù)庫(DB)中都沒有的數(shù)據(jù),出于容錯考慮從數(shù)據(jù)庫(DB)查不到數(shù)據(jù)則不寫入緩存(Redis)這將導(dǎo)致每個請求都要到數(shù)據(jù)庫(DB)中查詢,失去了緩存的意義,從而導(dǎo)致數(shù)據(jù)庫因壓力過大掛掉。
流程圖如下所示:
解決方案
1.對空值緩存
上面我們也介紹了,之所以會發(fā)生穿透,是因?yàn)榫彺嬷袥]有存儲這些空數(shù)據(jù)的key,從而導(dǎo)致每次查詢都到數(shù)據(jù)庫去了。
那么我們就可以為這些key的值設(shè)置null丟到緩存里面去,后面再出現(xiàn)查詢這個key 的請求的時候,直接返回null ,就不用在到數(shù)據(jù)庫中去走一圈了。但是別忘了設(shè)置過期時間。
關(guān)鍵代碼如下:
2.添加參數(shù)校驗(yàn)
我們可以在接口層添加校驗(yàn),不合法的直接返回即可,沒必要做后續(xù)的操作。
例如:使用bitmaps類型定義一個可以訪問名單,名單id作為bitmaps的偏移量,每次訪問時與bitmaps中的id進(jìn)行比較,如果訪問id不在bitmaps中,則進(jìn)行攔截,不給其訪問。
3.采用布隆過濾器
布隆過濾器(Bloom Filter),Bloom Filter 類似于一個hash set 用來判斷某個元素(key)是否存在于某個集合中,不存在return就好了,存在就去查DB刷新緩存KV再return,它的優(yōu)點(diǎn)是空間效率和查詢時間都比一般算法快,缺點(diǎn)是有一定的誤識別率和刪除困難。
布隆過濾器的工作方式:
一個空的布隆過濾器是一個由m個二進(jìn)制位構(gòu)成的數(shù)組。
以上只是畫了布隆過濾器的很小很小的一部分,實(shí)際布隆過濾器是非常大的數(shù)組(這里的大是指它的長度大,并不是指它所占的內(nèi)存空間大)。
當(dāng)一個數(shù)據(jù)進(jìn)行存入布隆過濾器的時候,會經(jīng)過若干個哈希函數(shù)進(jìn)行哈希,得到對應(yīng)的哈希值作為數(shù)組的下標(biāo),然后將初始化的位數(shù)組對應(yīng)的下標(biāo)的值修改為1,結(jié)果圖如下:
當(dāng)再次進(jìn)行存入第二個值的時候,修改后的結(jié)果的原理圖如下:
那么為什么會有誤判率呢?
假設(shè)在我們多次存入值后,在布隆過濾器中存在x、y、z這三個值,布隆過濾器的存儲結(jié)構(gòu)圖如下所示:
當(dāng)我們要查詢的時候,比如查詢M這個數(shù),實(shí)際中M這個數(shù)是不存在布隆過濾器中的,經(jīng)過哈希函數(shù)計算后得到M的哈希值分別為1和7,結(jié)構(gòu)原理圖如下:
經(jīng)過查詢后,發(fā)現(xiàn)1和7位置所存儲的值都為1,但是1和7的下標(biāo)分別是X和Z經(jīng)過計算后的下標(biāo)位置的修改,該布隆過濾器中實(shí)際不存在M,那么布隆過濾器就會誤判改值可能存在,因?yàn)椴悸∵^濾器不存元素值,所以存在誤判率。
那么為什么不能刪除元素呢?
原因很簡單,因?yàn)閯h除元素后,將對應(yīng)元素的下標(biāo)設(shè)置為零,可能別的元素的下標(biāo)也引用改下標(biāo),這樣別的元素的判斷就會受到影響。
Redis緩存雪崩
緩存雪崩是指大量的應(yīng)用請求無法在Redis緩存中進(jìn)行處理,緊接著應(yīng)用將大量請求發(fā)送到數(shù)據(jù)庫層,導(dǎo)致數(shù)據(jù)庫層的壓力激增。
緩存雪崩一般是由兩個原因?qū)е碌?,?yīng)對方案也有所不同。第一個原因是:緩存中有大量數(shù)據(jù)同時過期,導(dǎo)致大量請求無法得到處理。第二個原因是:Redis 緩存實(shí)例發(fā)生故障宕機(jī)了,無法處理請求,這就會導(dǎo)致大量請求一下子積壓到數(shù)據(jù)庫層,從而發(fā)生緩存雪崩。
流程圖如下所示:
解決方案
1.大量熱點(diǎn)數(shù)據(jù)同時失效帶來的緩存雪崩問題
避免熱key同時失效
使用 EXPIRE命令給每個數(shù)據(jù)設(shè)置過期時間時,給這些數(shù)據(jù)的過期時間增加一個較小的隨機(jī)數(shù)(例如,隨機(jī)增加 1~3 分鐘)。這樣一來,不同數(shù)據(jù)的過期時間有所差別,但差別又不會太大。既避免了大量數(shù)據(jù)同時過期,同時也保證了這些數(shù)據(jù)基本在相近的時間失效,仍然能滿足業(yè)務(wù)需求。
2. 服務(wù)降級
所謂的服務(wù)降級,是指發(fā)生緩存雪崩時,針對不同的數(shù)據(jù)采取不同的處理方式,例如:
當(dāng)業(yè)務(wù)應(yīng)用訪問的是非核心數(shù)據(jù)時,暫時停止從緩存中查詢這些數(shù)據(jù),而是直接返回預(yù)定義信息、空值或是錯誤信息;
當(dāng)業(yè)務(wù)應(yīng)用訪問的是核心數(shù)據(jù)時,仍然允許查詢緩存,如果緩存缺失,也可以繼續(xù)通過數(shù)據(jù)庫讀取。
這樣一來,我們就避免了大量請求因緩存缺失,而積壓到數(shù)據(jù)庫系統(tǒng),保證了數(shù)據(jù)庫系統(tǒng)的正常運(yùn)行。
3. Redis 緩存實(shí)例發(fā)生故障宕機(jī)帶來的緩存雪崩問題
從事前預(yù)防的角度,我們可以通過主從節(jié)點(diǎn)的方式構(gòu)建 Redis 緩存高可靠集群。如果 Redis緩存的主節(jié)點(diǎn)故障宕機(jī)了,從節(jié)點(diǎn)還可以切換成為主節(jié)點(diǎn),繼續(xù)提供緩存服務(wù),避免了由于緩存實(shí)例宕機(jī)而導(dǎo)致的緩存雪崩問題。
如果實(shí)際業(yè)務(wù)系統(tǒng)真發(fā)生了Redis 緩存實(shí)例不可用的情況,我們可以在業(yè)務(wù)系統(tǒng)中實(shí)現(xiàn)服務(wù)熔斷或請求限流機(jī)制。所謂的服務(wù)熔斷,是指在發(fā)生緩存雪崩時,為了防止引發(fā)連鎖的數(shù)據(jù)庫雪崩,甚至是整個系統(tǒng)的崩潰,我們暫停業(yè)務(wù)應(yīng)用對緩存系統(tǒng)的接口訪問。
Redis緩存擊穿
我們在平常高并發(fā)的系統(tǒng)中,大量的請求同時查詢一個key時,假設(shè)此時這個key正好失效了,就會導(dǎo)致大量的請求都打到數(shù)據(jù)庫上面去,這種現(xiàn)象我們稱為擊穿。
這么看緩存擊穿和緩存雪崩有點(diǎn)像,但是又有一點(diǎn)不一樣,緩存雪崩是因?yàn)榇竺娣e的緩存失效,打崩了DB,而緩存擊穿不同的是「緩存擊穿」是指一個Key非常熱點(diǎn),在不停的扛著大并發(fā),大并發(fā)集中對這一個點(diǎn)進(jìn)行訪問,當(dāng)這個Key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,就像在一個完好無損的桶上鑿開了一個洞,如下圖所示:
解決方案
1. 熱key不過期
預(yù)先設(shè)置熱門數(shù)據(jù):在Redis高峰訪問時期,提前設(shè)置熱門數(shù)據(jù)到緩存中,對這些熱key不設(shè)置失效時間,不過這樣設(shè)置需要區(qū)分場景。
實(shí)時調(diào)整:實(shí)時監(jiān)控哪些數(shù)據(jù)熱門,實(shí)時調(diào)整key過期時間。
2. 分布式鎖
為了避免出現(xiàn)緩存擊穿的情況,我們可以在第一個請求去查詢數(shù)據(jù)庫的時候?qū)λ右粋€分布式鎖,其余的查詢請求都會被阻塞住,直到鎖被釋放,后面的線程進(jìn)來發(fā)現(xiàn)已經(jīng)有緩存了,就直接走緩存,從而保護(hù)數(shù)據(jù)庫。但是也是由于它會阻塞其他的線程,此時系統(tǒng)吞吐量會下降。需要結(jié)合實(shí)際的業(yè)務(wù)去考慮是否要這么做。
關(guān)鍵代碼如下:
總結(jié)
緩存擊穿
key對應(yīng)的數(shù)據(jù)存在,但在redis中過期,此時若有大量并發(fā)請求過來,這些請求發(fā)現(xiàn)緩存過期一般都會從后端DB加載數(shù)據(jù)并回設(shè)到緩存,這個時候大并發(fā)的請求可能會瞬間把后端DB壓垮。一般通過互斥鎖,熱點(diǎn)數(shù)據(jù)永不過期,定時刷新過期時間等方法解決該問題。
緩存穿透
key對應(yīng)的數(shù)據(jù)在數(shù)據(jù)源并不存在,每次針對此key的請求從緩存獲取不到,請求都會到數(shù)據(jù)源,從而可能壓垮數(shù)據(jù)源。比如用一個不存在的用戶id獲取用戶信息,不論緩存還是數(shù)據(jù)庫都沒有,若黑客利用此漏洞進(jìn)行攻擊可能壓垮數(shù)據(jù)庫。一般通過對空數(shù)據(jù)進(jìn)行緩存,布隆過濾器等方法解決該問題。
緩存雪崩
當(dāng)緩存服務(wù)器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給后端系統(tǒng)(比如DB)帶來很大壓力。一般通過加鎖排隊,設(shè)置過期時間隨機(jī)值等方法解決該問題。
到此這篇關(guān)于淺談Redis緩存擊穿、緩存穿透、緩存雪崩的解決方案的文章就介紹到這了,更多相關(guān)Redis緩存擊穿解決方案內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis為什么默認(rèn)有16個數(shù)據(jù)庫問題
這篇文章主要介紹了Redis為什么默認(rèn)有16個數(shù)據(jù)庫問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02Redis6 主從復(fù)制及哨兵機(jī)制的實(shí)現(xiàn)
本文主要介紹了Redis6 主從復(fù)制及哨兵機(jī)制的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07詳解redis腳本命令執(zhí)行問題(redis.call)
這篇文章主要介紹了redis腳本命令執(zhí)行問題(redis.call),分別介紹了redis-cli命令行中執(zhí)行及l(fā)inux命令行中執(zhí)行問題,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-03-03關(guān)于redis狀態(tài)監(jiān)控和性能調(diào)優(yōu)詳解
Redis是一種高級key-value數(shù)據(jù)庫。它跟memcached類似,不過數(shù)據(jù)可以持久化,而且支持的數(shù)據(jù)類型很豐富。有字符串,鏈表、哈希、集合和有序集合5種。下面這篇文章主要給大家介紹了關(guān)于redis狀態(tài)監(jiān)控和性能調(diào)優(yōu)的相關(guān)資料,需要的朋友可以參考下。2017-09-09深入理解Redis內(nèi)存回收和內(nèi)存淘汰機(jī)制
Redis使用多種過期策略和內(nèi)存淘汰機(jī)制來管理內(nèi)存,本文主要介紹了深入理解Redis內(nèi)存回收和內(nèi)存淘汰機(jī)制, 具有一定的參考價值,感興趣的可以了解一下2024-06-06