Redis缓存穿透、击穿、雪崩、预热、更新、降级
Redis是高性能的分布式內(nèi)存數(shù)據(jù)庫(kù),對(duì)于內(nèi)存數(shù)據(jù)庫(kù)經(jīng)常會(huì)出現(xiàn)下面幾種情況,也經(jīng)常會(huì)出現(xiàn)在Redis面試題中:緩存穿透、緩存擊穿、緩存雪崩、緩存預(yù)熱、緩存更新、緩存降級(jí)。本篇分別介紹這些概念以及對(duì)應(yīng)的解決方案。
一、緩存穿透
當(dāng)查詢Redis中沒(méi)有的數(shù)據(jù)時(shí),該查詢會(huì)下沉到數(shù)據(jù)庫(kù)層,同時(shí)數(shù)據(jù)庫(kù)層也沒(méi)有該數(shù)據(jù),當(dāng)這種情況大量出現(xiàn)或被惡意攻擊時(shí),接口的訪問(wèn)全部透過(guò)Redis訪問(wèn)數(shù)據(jù)庫(kù),而數(shù)據(jù)庫(kù)中也沒(méi)有這些數(shù)據(jù),我們稱這種現(xiàn)象為"緩存穿透"。緩存穿透會(huì)穿透Redis的保護(hù),提升底層數(shù)據(jù)庫(kù)的負(fù)載壓力,同時(shí)這類穿透查詢沒(méi)有數(shù)據(jù)返回也造成了網(wǎng)絡(luò)和計(jì)算資源的浪費(fèi)。
解決方案:
- 在接口訪問(wèn)層對(duì)用戶做校驗(yàn),如接口傳參、登陸狀態(tài)、n秒內(nèi)訪問(wèn)接口的次數(shù);
- 利用布隆過(guò)濾器,將數(shù)據(jù)庫(kù)層有的數(shù)據(jù)key存儲(chǔ)在位數(shù)組中,以判斷訪問(wèn)的key在底層數(shù)據(jù)庫(kù)中是否存在;
第一種解決方案很好理解,這里介紹一下第二種方案,我們知道布隆過(guò)濾器可以判斷key一定不在集合內(nèi)以及key極有可能在集合內(nèi)。
基于布隆過(guò)濾器,我們可以先將數(shù)據(jù)庫(kù)中數(shù)據(jù)的key存儲(chǔ)在布隆過(guò)濾器的位數(shù)組中,每次客戶端查詢數(shù)據(jù)時(shí)先訪問(wèn)Redis:
布隆過(guò)濾器有誤判率,雖然不能完全避免數(shù)據(jù)穿透的現(xiàn)象,但已經(jīng)可以將99.99%的穿透查詢給屏蔽在Redis層了,極大的降低了底層數(shù)據(jù)庫(kù)的壓力,減少了資源浪費(fèi)。
二、緩存擊穿
緩存擊穿和緩存穿透從名詞上可能很難區(qū)分開(kāi)來(lái),它們的區(qū)別是:穿透表示底層數(shù)據(jù)庫(kù)沒(méi)有數(shù)據(jù)且緩存內(nèi)也沒(méi)有數(shù)據(jù),擊穿表示底層數(shù)據(jù)庫(kù)有數(shù)據(jù)而緩存內(nèi)沒(méi)有數(shù)據(jù)。當(dāng)熱點(diǎn)數(shù)據(jù)key從緩存內(nèi)失效時(shí),大量訪問(wèn)同時(shí)請(qǐng)求這個(gè)數(shù)據(jù),就會(huì)將查詢下沉到數(shù)據(jù)庫(kù)層,此時(shí)數(shù)據(jù)庫(kù)層的負(fù)載壓力會(huì)驟增,我們稱這種現(xiàn)象為"緩存擊穿"。
解決方案:
- 延長(zhǎng)熱點(diǎn)key的過(guò)期時(shí)間或者設(shè)置永不過(guò)期,如排行榜,首頁(yè)等一定會(huì)有高并發(fā)的接口;
- 利用互斥鎖保證同一時(shí)刻只有一個(gè)客戶端可以查詢底層數(shù)據(jù)庫(kù)的這個(gè)數(shù)據(jù),一旦查到數(shù)據(jù)就緩存至Redis內(nèi),避免其他大量請(qǐng)求同時(shí)穿過(guò)Redis訪問(wèn)底層數(shù)據(jù)庫(kù);
在使用互斥鎖的時(shí)候需要避免出現(xiàn)死鎖或者鎖過(guò)期的情況:
- 使用前面文章介紹過(guò)的lua腳本或事務(wù)將獲取鎖和設(shè)置過(guò)期時(shí)間作為一個(gè)原子性操作(如:set kk vv nx px
30000),以避免出現(xiàn)某個(gè)客戶端獲取鎖之后宕機(jī)導(dǎo)致的鎖不被釋放造成死鎖現(xiàn)象; - 另起一個(gè)線程監(jiān)控獲取鎖的線程的查詢狀態(tài),快到鎖過(guò)期時(shí)間時(shí)還沒(méi)查詢結(jié)束則延長(zhǎng)鎖的過(guò)期時(shí)間,避免多次查詢多次鎖過(guò)期造成計(jì)算資源的浪費(fèi);
三、緩存雪崩
緩存雪崩是緩存擊穿的"大面積"版,緩存擊穿是數(shù)據(jù)庫(kù)緩存到Redis內(nèi)的熱點(diǎn)數(shù)據(jù)失效導(dǎo)致大量并發(fā)查詢穿過(guò)redis直接擊打到底層數(shù)據(jù)庫(kù),而緩存雪崩是指Redis中大量的key幾乎同時(shí)過(guò)期,然后大量并發(fā)查詢穿過(guò)redis擊打到底層數(shù)據(jù)庫(kù)上,此時(shí)數(shù)據(jù)庫(kù)層的負(fù)載壓力會(huì)驟增,我們稱這種現(xiàn)象為"緩存雪崩"。事實(shí)上緩存雪崩相比于緩存擊穿更容易發(fā)生,對(duì)于大多數(shù)公司來(lái)講,同時(shí)超大并發(fā)量訪問(wèn)同一個(gè)過(guò)時(shí)key的場(chǎng)景的確太少見(jiàn)了,而大量key同時(shí)過(guò)期,大量用戶訪問(wèn)這些key的幾率相比緩存擊穿來(lái)說(shuō)明顯更大。
解決方案:
- 在可接受的時(shí)間范圍內(nèi)隨機(jī)設(shè)置key的過(guò)期時(shí)間,分散key的過(guò)期時(shí)間,以防止大量的key在同一時(shí)刻過(guò)期;
- 對(duì)于一定要在固定時(shí)間讓key失效的場(chǎng)景(例如每日12點(diǎn)準(zhǔn)時(shí)更新所有最新排名),可以在固定的失效時(shí)間時(shí)在接口服務(wù)端設(shè)置隨機(jī)延時(shí),將請(qǐng)求的時(shí)間打散,讓一部分查詢先將數(shù)據(jù)緩存起來(lái);
- 延長(zhǎng)熱點(diǎn)key的過(guò)期時(shí)間或者設(shè)置永不過(guò)期,這一點(diǎn)和緩存擊穿中的方案一樣;
四、緩存預(yù)熱
緩存預(yù)熱如字面意思,當(dāng)系統(tǒng)上線時(shí),緩存內(nèi)還沒(méi)有數(shù)據(jù),如果直接提供給用戶使用,每個(gè)請(qǐng)求都會(huì)穿過(guò)緩存去訪問(wèn)底層數(shù)據(jù)庫(kù),如果并發(fā)大的話,很有可能在上線當(dāng)天就會(huì)宕機(jī),因此我們需要在上線前先將數(shù)據(jù)庫(kù)內(nèi)的熱點(diǎn)數(shù)據(jù)緩存至Redis內(nèi)再提供出去使用,這種操作就成為"緩存預(yù)熱"。
緩存預(yù)熱的實(shí)現(xiàn)方式有很多,比較通用的方式是寫(xiě)個(gè)批任務(wù),在啟動(dòng)項(xiàng)目時(shí)或定時(shí)去觸發(fā)將底層數(shù)據(jù)庫(kù)內(nèi)的熱點(diǎn)數(shù)據(jù)加載到緩存內(nèi)。
五、緩存更新
緩存服務(wù)(Redis)和數(shù)據(jù)服務(wù)(底層數(shù)據(jù)庫(kù))是相互獨(dú)立且異構(gòu)的系統(tǒng),在更新緩存或更新數(shù)據(jù)的時(shí)候無(wú)法做到原子性的同時(shí)更新兩邊的數(shù)據(jù),因此在并發(fā)讀寫(xiě)或第二步操作異常時(shí)會(huì)遇到各種數(shù)據(jù)不一致的問(wèn)題。如何解決并發(fā)場(chǎng)景下更新操作的雙寫(xiě)一致是緩存系統(tǒng)的一個(gè)重要知識(shí)點(diǎn)。
第二步操作異常:緩存和數(shù)據(jù)的操作順序中,第二個(gè)動(dòng)作報(bào)錯(cuò)。如數(shù)據(jù)庫(kù)被更新, 此時(shí)失效緩存的時(shí)候出錯(cuò),緩存內(nèi)數(shù)據(jù)仍是舊版本;
緩存更新的設(shè)計(jì)模式有四種:
Cache aside:查詢:先查緩存,緩存沒(méi)有就查數(shù)據(jù)庫(kù),然后加載至緩存內(nèi);更新:先更新數(shù)據(jù)庫(kù),然后讓緩存失效;或者先失效緩存然后更新數(shù)據(jù)庫(kù);
Read through:在查詢操作中更新緩存,即當(dāng)緩存失效時(shí),Cache Aside 模式是由調(diào)用方負(fù)責(zé)把數(shù)據(jù)加載入緩存,而 Read Through 則用緩存服務(wù)自己來(lái)加載;
Write through:在更新數(shù)據(jù)時(shí)發(fā)生。當(dāng)有數(shù)據(jù)更新的時(shí)候,如果沒(méi)有命中緩存,直接更新數(shù)據(jù)庫(kù),然后返回。如果命中了緩存,則更新緩存,然后由緩存自己更新數(shù)據(jù)庫(kù);
Write behind caching:俗稱write back,在更新數(shù)據(jù)的時(shí)候,只更新緩存,不更新數(shù)據(jù)庫(kù),緩存會(huì)異步地定時(shí)批量更新數(shù)據(jù)庫(kù);
Cache aside:
- 為了避免在并發(fā)場(chǎng)景下,多個(gè)請(qǐng)求同時(shí)更新同一個(gè)緩存導(dǎo)致臟數(shù)據(jù),因此不能直接更新緩存而是另緩存失效。
- 先更新數(shù)據(jù)庫(kù)后失效緩存:并發(fā)場(chǎng)景下,推薦使用延遲失效(寫(xiě)請(qǐng)求完成后給緩存設(shè)置1s過(guò)期時(shí)間),在讀請(qǐng)求緩存數(shù)據(jù)時(shí)若redis內(nèi)已有該數(shù)據(jù)(其他寫(xiě)請(qǐng)求還未結(jié)束)則不更新。當(dāng)redis內(nèi)沒(méi)有該數(shù)據(jù)的時(shí)候(其他寫(xiě)請(qǐng)求已令該緩存失效),讀請(qǐng)求才會(huì)更新redis內(nèi)的數(shù)據(jù)。這里的讀請(qǐng)求緩存數(shù)據(jù)可以加上失效時(shí)間,以防第二步操作異常導(dǎo)致的不一致情況。
- 先失效緩存后更新數(shù)據(jù)庫(kù):并發(fā)場(chǎng)景下,推薦使用延遲失效(寫(xiě)請(qǐng)求開(kāi)始前給緩存設(shè)置1s過(guò)期時(shí)間),在寫(xiě)請(qǐng)求失效緩存時(shí)設(shè)置一個(gè)1s延遲時(shí)間,然后再去更新數(shù)據(jù)庫(kù)的數(shù)據(jù),此時(shí)其他讀請(qǐng)求仍然可以讀到緩存內(nèi)的數(shù)據(jù),當(dāng)數(shù)據(jù)庫(kù)端更新完成后,緩存內(nèi)的數(shù)據(jù)已失效,之后的讀請(qǐng)求會(huì)將數(shù)據(jù)庫(kù)端最新的數(shù)據(jù)加載至緩存內(nèi)保證緩存和數(shù)據(jù)庫(kù)端數(shù)據(jù)一致性;在這種方案下,第二步操作異常不會(huì)引起數(shù)據(jù)不一致,例如設(shè)置了緩存1s后失效,然后在更新數(shù)據(jù)庫(kù)時(shí)報(bào)錯(cuò),即使緩存失效,之后的讀請(qǐng)求仍然會(huì)把更新前的數(shù)據(jù)重新加載到緩存內(nèi)。
推薦使用先失效緩存,后更新數(shù)據(jù)庫(kù),配合延遲失效來(lái)更新緩存的模式;
四種緩存更新模式的優(yōu)缺點(diǎn):
- Cache Aside:實(shí)現(xiàn)起來(lái)較簡(jiǎn)單,但需要維護(hù)兩個(gè)數(shù)據(jù)存儲(chǔ),一個(gè)是緩存(Cache),一個(gè)是數(shù)據(jù)庫(kù)(Repository);
- Read/Write Through:只需要維護(hù)一個(gè)數(shù)據(jù)存儲(chǔ)(緩存),但是實(shí)現(xiàn)起來(lái)要復(fù)雜一些;
- Write Behind Caching:與Read/Write Through 類似,區(qū)別是Write Behind
Caching的數(shù)據(jù)持久化操作是異步的,但是Read/Write Through 更新模式的數(shù)據(jù)持久化操作是同步的。優(yōu)點(diǎn)是直接操作內(nèi)存速度快,多次操作可以合并持久化到數(shù)據(jù)庫(kù)。缺點(diǎn)是數(shù)據(jù)可能會(huì)丟失,例如系統(tǒng)斷電等。緩存本身就是通過(guò)犧牲強(qiáng)一致性來(lái)提高性能,因此使用緩存提升性能,就會(huì)有數(shù)據(jù)更新的延遲性。這就需要我們?cè)谠u(píng)估需求和設(shè)計(jì)階段根據(jù)實(shí)際場(chǎng)景去做權(quán)衡了。
六、緩存降級(jí)
緩存降級(jí)是指當(dāng)訪問(wèn)量劇增、服務(wù)出現(xiàn)問(wèn)題(如響應(yīng)時(shí)間慢或不響應(yīng))或非核心服務(wù)影響到核心流程的性能時(shí),即使是有損部分其他服務(wù),仍然需要保證主服務(wù)可用。可以將其他次要服務(wù)的數(shù)據(jù)進(jìn)行緩存降級(jí),從而提升主服務(wù)的穩(wěn)定性。降級(jí)的目的是保證核心服務(wù)可用,即使是有損的。如去年雙十一的時(shí)候淘寶購(gòu)物車無(wú)法修改地址只能使用默認(rèn)地址,這個(gè)服務(wù)就是被降級(jí)了,這里阿里保證了訂單可以正常提交和付款,但修改地址的服務(wù)可以在服務(wù)器壓力降低,并發(fā)量相對(duì)減少的時(shí)候再恢復(fù)。降級(jí)可以根據(jù)實(shí)時(shí)的監(jiān)控?cái)?shù)據(jù)進(jìn)行自動(dòng)降級(jí)也可以配置開(kāi)關(guān)人工降級(jí)。是否需要降級(jí),哪些服務(wù)需要降級(jí),在什么情況下再降級(jí),取決于大家對(duì)于系統(tǒng)功能的取舍。
文章轉(zhuǎn)自
總結(jié)
以上是生活随笔為你收集整理的Redis缓存穿透、击穿、雪崩、预热、更新、降级的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 分享个竞品分析报告
- 下一篇: linux cmake编译源码,linu