Redis緩存問(wèn)題與緩存更新機(jī)制詳解
一、緩存問(wèn)題
1.1 緩存穿透
1.1.1 問(wèn)題來(lái)源
緩存穿透是指緩存和數(shù)據(jù)庫(kù)中都沒(méi)有的數(shù)據(jù),而用戶不斷發(fā)起請(qǐng)求。由于緩存是不命中時(shí)被動(dòng)寫(xiě)的,并且出于容錯(cuò)考慮,如果從存儲(chǔ)層查不到數(shù)據(jù)則不寫(xiě)入緩存,這將導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請(qǐng)求都要到存儲(chǔ)層去查詢,失去了緩存的意義。在流量大時(shí),可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應(yīng)用,這就是漏洞。
1.1.2 解決方案
1.1.2.1 緩存空對(duì)象
- 從緩存取不到的數(shù)據(jù),在數(shù)據(jù)庫(kù)中也沒(méi)有取到,這時(shí)也可以將key-value對(duì)寫(xiě)為key-null,緩存有效時(shí)間可以設(shè)置短點(diǎn),如30秒(設(shè)置太長(zhǎng)會(huì)導(dǎo)致正常情況也沒(méi)法使用)。
- 這樣可以防止攻擊用戶反復(fù)用同一個(gè)id暴力攻擊。
1.1.2.2 使用布隆過(guò)濾器
- 類似于一個(gè)hash set,用于快速判某個(gè)元素是否存在于集合中,其典型的應(yīng)用場(chǎng)景就是快速判斷一個(gè)key是否存在于某容器,不存在就直接返回。
- 布隆過(guò)濾器的關(guān)鍵就在于hash算法和容器大小。
1.2 緩存擊穿
1.2.1 問(wèn)題來(lái)源
緩存擊穿是指緩存某些熱點(diǎn)數(shù)據(jù)失效(一般是緩存時(shí)間到期),這時(shí)由于并發(fā)用戶特別多,同時(shí)讀緩存沒(méi)讀到數(shù)據(jù),又同時(shí)去數(shù)據(jù)庫(kù)去取數(shù)據(jù),引起數(shù)據(jù)庫(kù)壓力瞬間增大,造成過(guò)大壓力。
1.2.2 解決方案
1.2.2.1 設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過(guò)期
可以在刷緩存時(shí),設(shè)置熱點(diǎn)數(shù)據(jù)不過(guò)期。
1.2.2.2 新增后臺(tái)定時(shí)更新緩存線程(邏輯不過(guò)期)
后臺(tái)新增一個(gè)緩存更新線程,緩存快要過(guò)期前刷新緩存時(shí)間,防止緩存失效。
1.2.2.3 使用分布式互斥鎖
可以使用Redis提供的分布式互斥鎖,保證只有一個(gè)請(qǐng)求查詢數(shù)據(jù)庫(kù)和更新緩存,其他請(qǐng)求阻塞等待緩存更新完成后在訪問(wèn)緩存。
1.2.2.4 接口限流與熔斷,降級(jí)
重要的接口一定要做好限流策略,防止用戶惡意刷接口,同時(shí)要降級(jí)準(zhǔn)備,當(dāng)接口中的某些服務(wù)不可用時(shí)候,進(jìn)行熔斷,失敗快速返回機(jī)制。
1.3 緩存雪崩
1.3.1 問(wèn)題來(lái)源
緩存雪崩是指Redis緩存不能正常提供服務(wù)了(阻塞、服務(wù)宕機(jī)、大面積緩存失效等造成),導(dǎo)致所有請(qǐng)求都落到了數(shù)據(jù)庫(kù)上,增加了數(shù)據(jù)庫(kù)壓力或者導(dǎo)致數(shù)據(jù)庫(kù)宕機(jī)。
1.3.2 解決方案
1.3.2.1 緩存過(guò)期時(shí)間隨機(jī)
緩存數(shù)據(jù)的過(guò)期時(shí)間設(shè)置隨機(jī),防止同一時(shí)間大量數(shù)據(jù)過(guò)期現(xiàn)象發(fā)生。
1.3.2.2 分布式部署
采用分布式部署方式部署緩存,避免緩存服務(wù)單節(jié)點(diǎn),同時(shí)將熱點(diǎn)數(shù)據(jù)均勻分布在不同的緩存數(shù)據(jù)庫(kù)中。
1.3.2.3 設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過(guò)期
可以在刷緩存時(shí),設(shè)置熱點(diǎn)數(shù)據(jù)不過(guò)期。
1.3.2.4 接口限流與熔斷,降級(jí)
重要的接口一定要做好限流策略,防止用戶惡意刷接口,同時(shí)要降級(jí)準(zhǔn)備,當(dāng)接口中的某些服務(wù)不可用時(shí)候,進(jìn)行熔斷,失敗快速返回機(jī)制。
二、緩存更新機(jī)制
2.1 緩存更新策略分類
內(nèi)存淘汰 | 超時(shí)剔除 | 主動(dòng)更新 | |
說(shuō)明 | 重要的接口一定要做好限流策略,防止用戶惡意刷接口,同時(shí)要降級(jí)準(zhǔn)備,當(dāng)接口中的某些服務(wù)不可用時(shí)候,進(jìn)行熔斷,失敗快速返回機(jī)制。 | 給緩存數(shù)據(jù)添加TTL時(shí)間,到期后自動(dòng)刪除緩存,下次查詢時(shí)更新緩存 | 編寫(xiě)業(yè)務(wù)邏輯,在修改數(shù)據(jù)的同時(shí),更新緩存 |
一致性 | 差 | 一般 | 好 |
維護(hù)成本 | 無(wú) | 低 | 高 |
2.2 內(nèi)存淘汰機(jī)制
2.2.1 noeviction
不淘汰,這是默認(rèn)的淘汰策略;
當(dāng)內(nèi)存達(dá)到限制后,寫(xiě)請(qǐng)求(set)會(huì)返回錯(cuò)誤,讀請(qǐng)求(get)和刪除請(qǐng)求(del)可以繼續(xù)進(jìn)行
2.2.2 volatile-lru
內(nèi)存不足時(shí),在設(shè)置了過(guò)期時(shí)間的key中,優(yōu)先刪除最近最少使用的key
2.2.3 volatile-lfu
內(nèi)存不足時(shí),在設(shè)置了過(guò)期時(shí)間的key中,優(yōu)先刪除使用頻率最少的key
2.2.4 volatile-ttl
內(nèi)存不足時(shí),在設(shè)置了過(guò)期時(shí)間的key中,優(yōu)先刪除存活剩余時(shí)間最少的key
2.2.5 volatile-random
內(nèi)存不足時(shí),在設(shè)置了過(guò)期時(shí)間的key中,隨機(jī)刪除某個(gè)key
2.2.6 allkey-lru
內(nèi)存不足時(shí),在全體key范圍內(nèi),優(yōu)先刪除最近最少使用的key
2.2.7 allkey-lfu
內(nèi)存不足時(shí),在全體key范圍內(nèi),優(yōu)先刪除使用頻率最少的key
2.2.8 allkey-random
內(nèi)存不足時(shí),在全體key范圍內(nèi),隨機(jī)刪除某個(gè)key
2.3 超時(shí)剔除
2.3.1 定時(shí)刪除
設(shè)置一個(gè)定時(shí)任務(wù),隨機(jī)抽取部分過(guò)期時(shí)間的key,檢查是否過(guò)期,過(guò)期了就清除掉
2.3.2 惰性刪除
查詢獲取數(shù)據(jù)時(shí),檢查緩存是否過(guò)期,過(guò)期則刪除,沒(méi)過(guò)期不刪除
Redis 默認(rèn)采用惰性刪除+定時(shí)刪除結(jié)合的過(guò)期策略
2.4 主動(dòng)更新
2.4.1 主動(dòng)更新策略
2.4.1.1 Cache Aside Pattern
- 由緩存的調(diào)用者
- 在更新數(shù)據(jù)庫(kù)的同時(shí)更新緩存
2.4.1.2 Read/Write Through Pattern
- 緩存和數(shù)據(jù)庫(kù)整合為一個(gè)服務(wù),由服務(wù)來(lái)維護(hù)一致性。
- 調(diào)用者調(diào)用服務(wù),不用關(guān)心一致性問(wèn)題。
2.4.1.3 Write Behind Caching Pattern
調(diào)用者只操作緩存,由其他線程異步的將緩存數(shù)據(jù)持久化到數(shù)據(jù)庫(kù),最終保持一致。
在企業(yè)中使用最多的主動(dòng)更新策略是 Cache Aside Pattern。也就是我們自己編碼來(lái)保證數(shù)據(jù)的一致性。
2.4.2 主動(dòng)更新策略需要考慮的三個(gè)問(wèn)題
2.4.1 刪除緩存還是更新緩存?
- 2.4.1.1 刪除緩存
更新數(shù)據(jù)庫(kù)時(shí)讓緩存失效,查詢時(shí)再更新緩存。(延遲加載)一般選擇這個(gè)方案。
這個(gè)方案比較合理一點(diǎn),可以避免過(guò)多的無(wú)效寫(xiě)操作,緩存刪除后,只要沒(méi)人來(lái)查詢這條數(shù)據(jù),數(shù)據(jù)就不會(huì)被寫(xiě)入緩存,這樣就可以避免大量無(wú)效的寫(xiě)操作
- 2.4.1.2 更新緩存
每次更新數(shù)據(jù)庫(kù)都更新緩存,無(wú)效寫(xiě)操作比較多。
這種方式的缺點(diǎn)很明顯,舉個(gè)例子:假如我更新了100次數(shù)據(jù)庫(kù),然后又同時(shí)更新了100次緩存,但是在更新的時(shí)候并沒(méi)有人來(lái)查這個(gè)數(shù)據(jù),那么我更新這100次緩存好像也沒(méi)啥用吧,相當(dāng)于前99次都是無(wú)用功,只有最后一次才是有用的。這就是無(wú)效寫(xiě)操作過(guò)多的原因。
2.4.2 如何保證緩存與數(shù)據(jù)庫(kù)的操作同時(shí)成功或失???
1)單體系統(tǒng),將緩存與數(shù)據(jù)庫(kù)操作放在一個(gè)事務(wù)中。
2)分布式系統(tǒng),利用TCC等分布式事務(wù)方案。
2.4.3 先操作緩存還是數(shù)據(jù)庫(kù)?
- 2.4.3.1 先刪除緩存,再操作數(shù)據(jù)庫(kù)
這種方式存在很明顯的問(wèn)題,假設(shè)有兩個(gè)并發(fā)操作,線程A更新,線程B查詢。線程A先刪除緩存,然后還沒(méi)來(lái)得及更新數(shù)據(jù)庫(kù),CPU資源被線程B搶走,線程B查詢緩存發(fā)現(xiàn)沒(méi)有命中(因?yàn)橐呀?jīng)被線程A刪除了),查詢數(shù)據(jù)庫(kù),然后把結(jié)果寫(xiě)入到緩存中。這個(gè)時(shí)候線程A終于搶到CPU資源了,然后更新數(shù)據(jù)庫(kù),此時(shí)就會(huì)造成數(shù)據(jù)不一致問(wèn)題。
- 2.4.3.2 先操作數(shù)據(jù)庫(kù),再刪除緩存
這種處理方式使用的頻率是最高的,因?yàn)槌鲥e(cuò)的概率非常小,只有一種比較極端的情況才會(huì)出現(xiàn)數(shù)據(jù)一致性問(wèn)題。
同樣有兩個(gè)并發(fā)請(qǐng)求,線程A查詢、線程B更新,當(dāng)線程A查詢的時(shí)候,緩存剛好失效,然后就去查詢數(shù)據(jù)庫(kù)拿到數(shù)據(jù),在準(zhǔn)備寫(xiě)入緩存的時(shí)候,CPU資源被線程B搶走,線程B開(kāi)始更新數(shù)據(jù)庫(kù),然后刪除緩存(這一步其實(shí)等于無(wú)用,因?yàn)榫彺嬉呀?jīng)過(guò)期)。此時(shí)線程A再次獲取到CPU資源,然后寫(xiě)入緩存,此時(shí)寫(xiě)入的是更新前的舊數(shù)據(jù),會(huì)產(chǎn)生數(shù)據(jù)一致性問(wèn)題。
看起來(lái)這確實(shí)也是一個(gè)問(wèn)題,但是我們仔細(xì)分析一下這種情況都需要滿足哪些條件:
- 1)并發(fā)讀寫(xiě)操作
- 2)讀緩存時(shí),緩存剛好失效
- 3)寫(xiě)數(shù)據(jù)庫(kù)操作要比寫(xiě)緩存快
寫(xiě)數(shù)據(jù)庫(kù)是操作磁盤(pán),寫(xiě)緩存是操作內(nèi)存的,所以不太可能會(huì)出現(xiàn)寫(xiě)磁盤(pán)的速度快于寫(xiě)內(nèi)存的。因此使用這種方式出現(xiàn)數(shù)據(jù)一致性的概率是很小的。
- 2.4.3.3 延時(shí)雙刪策略
延遲雙刪策略是分布式系統(tǒng)中數(shù)據(jù)庫(kù)存儲(chǔ)和緩存數(shù)據(jù)保持一致性的常用策略,但它不是強(qiáng)一致。其實(shí)不管哪種方案,都避免不了Redis存在臟數(shù)據(jù)的問(wèn)題,只能減輕這個(gè)問(wèn)題,要想徹底解決,得要用到同步鎖和對(duì)應(yīng)的業(yè)務(wù)邏輯層面解決。
前面兩種方案的不足點(diǎn)我們進(jìn)行了分析,第二種方式的使用頻率比較高,但是也有一些小缺陷,雖然說(shuō)發(fā)生的概率很低,但是這個(gè)概率到了線上會(huì)不會(huì)發(fā)生也不好說(shuō),所以就有了延時(shí)雙刪策略對(duì)第二種方式做補(bǔ)充。
所謂延時(shí)雙刪就是先進(jìn)行緩存清除,再執(zhí)行數(shù)據(jù)庫(kù)操作,最后(延遲N秒)再執(zhí)行緩存清除。延遲N秒的時(shí)間要大于一次寫(xiě)操作的時(shí)間,這個(gè)延時(shí)N秒就是了完善保證第二種策略中不足,可以保證線程A的寫(xiě)緩存和線程B的修改數(shù)據(jù)庫(kù)、刪除緩存都執(zhí)行完畢,然后再刪除緩存一次,就可以保證后面再來(lái)的查詢請(qǐng)求可以查詢到最新數(shù)據(jù)。
ps: 一般的延時(shí)時(shí)間設(shè)置為3S左右,具體情況要根據(jù)業(yè)務(wù)場(chǎng)景取最佳值。
2.5 緩存更新機(jī)制總結(jié)
- 1)內(nèi)存淘汰:不用自己維護(hù),利用Redis內(nèi)存淘汰機(jī)制,自動(dòng)刪除部分緩存數(shù)據(jù),這些被刪除的數(shù)據(jù)在下一次被查詢時(shí)更新。這種方式一致性最差。
- 2)超時(shí)剔除:給緩存數(shù)據(jù)加上過(guò)期時(shí)間 ,到期后自動(dòng)刪除,下次查詢時(shí)更新,數(shù)據(jù)一致性問(wèn)題大概率會(huì)出現(xiàn)。維護(hù)成本比較低。
- 3)主動(dòng)更新:編寫(xiě)業(yè)務(wù)邏輯,在修改數(shù)據(jù)庫(kù)的同時(shí)更新緩存,一致性比較好,維護(hù)成本比較高。一般采用先操作數(shù)據(jù)庫(kù)再更新緩存的方式。
一般在數(shù)據(jù)一致性要求比較低的場(chǎng)景下可以使用內(nèi)存淘汰機(jī)制,比如商城首頁(yè)的分類信息,這些東西基本上是不會(huì)變化的。如果一致性要求比較高,我們可以采用主動(dòng)更新+超時(shí)剔除兜底的方式來(lái)處理。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Redis內(nèi)存空間占用及避免數(shù)據(jù)丟失的方法
在現(xiàn)代的互聯(lián)網(wǎng)應(yīng)用中,Redis作為一種高性能的內(nèi)存數(shù)據(jù)庫(kù),被廣泛應(yīng)用于緩存、會(huì)話管理和消息隊(duì)列等場(chǎng)景,然而,Redis的內(nèi)存資源是有限的,過(guò)多的內(nèi)存占用可能會(huì)導(dǎo)致數(shù)據(jù)丟失所以本文將給大家介紹一下Redis內(nèi)存空間占用及避免數(shù)據(jù)丟失的方法2023-08-08淺析Redis中String數(shù)據(jù)類型及其底層編碼
這篇文章主要介紹?Redis?中?String?數(shù)據(jù)類型及其底層編碼,文中有詳細(xì)的代碼示例,對(duì)大家的工作及學(xué)習(xí)有一定的幫助,需要的朋友可以參考下2023-05-05一起raid數(shù)據(jù)恢復(fù)及回遷成功的案例
這篇文章主要介紹了一起raid數(shù)據(jù)恢復(fù)及回遷成功的案例,需要的朋友可以參考下2017-04-04Redis遍歷所有key的兩個(gè)命令(KEYS 和 SCAN)
這篇文章主要介紹了Redis遍歷所有key的兩個(gè)命令(KEYS 和 SCAN),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04