一篇吃透Redis緩存穿透、雪崩、擊穿問題
前言:在學(xué)Redis之前我們查詢數(shù)據(jù)的時(shí)候都是直接查詢數(shù)據(jù)庫的,但是這樣會有一個(gè)潛在的問題:“如果用戶量很大,所有請求都去訪問數(shù)據(jù)庫,那么會使數(shù)據(jù)庫壓力過大,導(dǎo)致性能下降甚至宕機(jī)”。因此,我們需要把經(jīng)常訪問的數(shù)據(jù)放到緩存中,這里我們用Redis作為緩存。
但是,使用Redis作為緩存的過程中我們一般如下,如下圖:我們查詢某個(gè)數(shù)據(jù),前端發(fā)送請求到后端,后端根據(jù)請求去查詢數(shù)據(jù),一開始先去Redis中查有無這個(gè)數(shù)據(jù),如果有則直接返回,沒有則去數(shù)據(jù)庫中查找并且將查找到的結(jié)果返回,如果數(shù)據(jù)庫中沒有則返回錯(cuò)誤信息。

那么,在使用Redis作為緩存的過程中我們避免不了會遇到以下幾個(gè)常見的問題:①Redis與數(shù)據(jù)庫的數(shù)據(jù)一致性問題。②緩存穿透。③緩存雪崩。④緩存擊穿。
我們先來說①緩存與數(shù)據(jù)庫的數(shù)據(jù)庫一致性問題。
對于Redis作為緩存我們有以下用法:①只讀緩存。②讀寫緩存。
只讀緩存是指對于修改數(shù)據(jù)的時(shí)候,我們只對數(shù)據(jù)庫進(jìn)行修改,同時(shí)刪除緩存,這樣我們永遠(yuǎn)能保證數(shù)據(jù)庫中有最新的數(shù)據(jù),但是這樣每次修改數(shù)據(jù)的時(shí)候,我們查找該數(shù)據(jù)的時(shí)候都需要查找一次數(shù)據(jù)庫,因此性能會有所降低。
讀寫緩存是指我們修改數(shù)據(jù)的時(shí)候?qū)彺娴臄?shù)據(jù)也直接進(jìn)行修改,那么又可以分為兩種:①同步讀寫②異步讀寫。
同步讀寫是指修改數(shù)據(jù)的時(shí)候同時(shí)修改緩存和數(shù)據(jù)庫,并且通過事務(wù)的特性保證二者數(shù)據(jù)一致性,但是由于緩存的速度遠(yuǎn)快于數(shù)據(jù)庫的速度,因此性能還是會有一定的限制。
異步讀寫是指我們每次修改數(shù)據(jù)都是在緩存中修改,每隔一段時(shí)間將緩存中的數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫中,這樣每次修改的時(shí)間只是修改緩存數(shù)據(jù)的時(shí)間,性能大大提高。但是這樣會存在一個(gè)隱患:“當(dāng)Redis突發(fā)情況宕機(jī)的時(shí)候,數(shù)據(jù)還沒來得及導(dǎo)入數(shù)據(jù)庫,那么這段時(shí)間還沒保存進(jìn)數(shù)據(jù)庫的數(shù)據(jù)就會丟失”,因此一致性無法保證。
然后我們來說說②緩存穿透。由我們Redis作為緩存的流程圖可以知道:我們每次查詢數(shù)據(jù)的時(shí)候都是先查詢緩存,查詢不到再查數(shù)據(jù)庫,如果數(shù)據(jù)庫中也查詢不到則返回錯(cuò)誤信息。

那么,對于一個(gè)查詢不到的數(shù)據(jù),如果有心懷不軌的人,寫一個(gè)程序,多線程并發(fā)的無限查詢這個(gè)數(shù)據(jù),那么我們每次都需要訪問數(shù)據(jù)庫,會導(dǎo)致數(shù)據(jù)庫性能下降甚至宕機(jī),這就是緩存穿透。
那么,我們?nèi)绾蝸斫鉀Q緩存穿透呢?這里我們說兩種方法:
方法一:Redis緩存中存儲空值。我們可以知道,緩存穿透是由于某些不存在的數(shù)據(jù),每次查詢,我們在緩存中都查找不到該數(shù)據(jù),因此每次都需要去訪問數(shù)據(jù)庫。那么我們可以不可以對于這些數(shù)據(jù)也存入緩存中呢?這樣我們每次查找這些數(shù)據(jù),就只會第一次查找數(shù)據(jù)庫,后面都是查找緩存了。那么有人會問了?“咦,不存在的數(shù)據(jù)怎么查找呢?”所以我們這邊是將某個(gè)不存在的數(shù)據(jù)存儲在緩存中,并且存儲的值為空字符串(也可以是其他的,這里取決于你與前端兄弟的約定)。如下圖:

因此,這樣我們對于這些惡意數(shù)據(jù),就只能“騷擾”我們的寶貝數(shù)據(jù)庫一次了,便解決了緩存穿透的問題,那么還有一個(gè)疑問就是:如果將來這些數(shù)據(jù)有了,但是每次查詢的時(shí)候緩存都直接返回空不是嘛?不會的,因?yàn)槲覀円院髮τ跀?shù)據(jù)庫插入數(shù)據(jù)的同時(shí)會對Redis緩存進(jìn)行一個(gè)Set,這個(gè)Set數(shù)據(jù)的操作會直接覆蓋原有不存在的數(shù)據(jù),因此并不會出現(xiàn)這種問題。
方法二:布隆過濾器。大致就是在前端頁面和緩存直接多了一個(gè)布隆過濾器

布隆過濾器(Bloom Filter)是1970年由布隆提出的。它實(shí)際上是一個(gè)很長的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都比一般的算法要好的多,缺點(diǎn)是有一定的誤識別率和刪除困難。布隆過濾器可以告訴我們 “某樣?xùn)|西一定不存在或者可能存在”,也就是說布隆過濾器說這個(gè)數(shù)不存在則一定不存,布隆過濾器說這個(gè)數(shù)存在可能不存在。
③緩存雪崩,什么是緩存雪崩呢?緩存雪崩指的是在同一時(shí)間大量Redis緩存中存儲的key過期或者Redis服務(wù)器直接宕機(jī),并且大量的請求查詢這些數(shù)據(jù)的時(shí)候,會導(dǎo)致大量請求一窩蜂的涌向數(shù)據(jù)庫進(jìn)行查詢,導(dǎo)致數(shù)據(jù)庫壓力過大,性能下降甚至宕機(jī)。那么如何解決緩存雪崩呢?
對于情況一:大量key同時(shí)過期,這里說幾種解決方法:
①設(shè)置緩存key過期時(shí)間可以隨機(jī)設(shè)置,這樣不會使得大量的key同一時(shí)間段內(nèi)過期。
②服務(wù)降級:指的是對于一些非核心數(shù)據(jù)來說(比如查詢一些無關(guān)緊要的數(shù)據(jù)時(shí))我們可以預(yù)先設(shè)置一些值,當(dāng)無法訪問緩存的時(shí)候,這些數(shù)據(jù)不會直接查詢數(shù)據(jù)庫,而是返回預(yù)先設(shè)置的值,比如一些錯(cuò)誤信息。
對于情況二:Redis服務(wù)器直接宕機(jī),這里說幾種解決方法:
①搭建Redis服務(wù)集群:嘗試構(gòu)建 Redis 的高可用集群,比如當(dāng)某主節(jié)點(diǎn)掛掉了,集群能夠馬上重新選出新的主節(jié)點(diǎn)。
②業(yè)務(wù)中實(shí)現(xiàn)服務(wù)熔斷或者請求限流機(jī)制:
服務(wù)熔斷:如果監(jiān)聽到發(fā)生了緩存雪崩,直接暫停對緩存服務(wù)的請求,但是這種對業(yè)務(wù)的影響比較大;
服務(wù)限流:可以在入口做限流,不要讓所有的請求都流入到后端的服務(wù)中;
④緩存擊穿:緩存雪崩指的是大量數(shù)據(jù)無法從Redis查詢到,而同時(shí)去查詢數(shù)據(jù)庫導(dǎo)致,緩存擊穿則是某些熱點(diǎn)key,比如雙十一搶蘋果手機(jī),如果突然間Redis緩存對于這個(gè)數(shù)據(jù)過期了,那么這一瞬間大量搶蘋果手機(jī)的請求都會去訪問數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫性能下降甚至宕機(jī)這里我們講兩種解決方法:①Redis互斥鎖。②緩存數(shù)據(jù)邏輯過期。
方法一:Redis互斥鎖。以上我們知道:對于某個(gè)熱點(diǎn)key失效的時(shí)候,由于大量查詢該數(shù)據(jù)的請求在緩存中查找不到,因此同時(shí)查找數(shù)據(jù)庫導(dǎo)致。那么我們只需要對于每個(gè)數(shù)據(jù)設(shè)置一個(gè)互斥鎖,當(dāng)在訪問不到緩存的時(shí)候,只有一個(gè)線程能夠去訪問數(shù)據(jù)庫,其他線程等待,
- 這樣就解決了以上的問題。

但是這樣會導(dǎo)致一個(gè)問題:那就是由于其他線程都要等待,會導(dǎo)致性能下降,要等那個(gè)拿到互斥鎖的線程查詢完數(shù)據(jù)庫,并且返回?cái)?shù)據(jù)到緩存中才能查到數(shù)據(jù)。
方法二:緩存的數(shù)據(jù)邏輯過期。我們知道,緩存擊穿是由于熱點(diǎn)key過期導(dǎo)致去查詢數(shù)據(jù)庫,那么我們可以這樣想:如果這些熱點(diǎn)key只是邏輯過期(邏輯過期指雖然過期了,但是還是在緩存里面不會刪除,但是程序會知道是已過期的數(shù)據(jù),會訪問一次數(shù)據(jù)庫進(jìn)行更新),那么不就解決了嗎?因此邏輯過期的解決思路是:對于這些熱點(diǎn)key,先查詢緩存,如果沒過期則直接返回,如果過期了則開一個(gè)新的線程
去查詢數(shù)據(jù)庫,而查詢數(shù)據(jù)庫過程中訪問緩存的請求直接返回舊數(shù)據(jù)即可。

但是邏輯過期由于要多一個(gè)線程,因此有一定的內(nèi)存消耗,并且更新過程中訪問的請求都是收到舊的數(shù)據(jù),因此一致性有一定的下降。
以上是使用Redis緩存過程中的一些常見問題,后續(xù)會慢慢補(bǔ)充。
以上就是一篇吃透Redis緩存穿透、雪崩、擊穿問題的詳細(xì)內(nèi)容,更多關(guān)于Redis緩存穿透、雪崩、擊穿的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
阿里云服務(wù)器安裝配置redis的方法并且加入到開機(jī)啟動(推薦)
這篇文章主要介紹了阿里云服務(wù)器安裝配置redis并且加入到開機(jī)啟動,需要的朋友可以參考下2017-12-12
Springboot整合Redis與數(shù)據(jù)持久化
這篇文章主要介紹了Springboot整合Redis與Redis數(shù)據(jù)持久化的操作,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
Redis之如何實(shí)現(xiàn)用戶關(guān)注
這篇文章主要介紹了Redis之如何實(shí)現(xiàn)用戶關(guān)注問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
ubuntu 16.04安裝redis的兩種方式教程詳解(apt和編譯方式)
這篇文章主要介紹了ubuntu 16.04安裝redis的兩種方式教程詳解(apt和編譯方式),需要的朋友可以參考下2018-03-03
RedisDesktopManager?連接redis的方法
這篇文章主要介紹了RedisDesktopManager?連接redis,需要的朋友可以參考下2023-08-08

