Redis緩存及熱點key問題解決方案
今天又學(xué)到了很多,感覺雪崩和穿透很有意思理解起來也比較清晰,然后我搜索了一些資料,給自己做一個普及
我們通常使用 緩存 + 過期時間的策略來幫助我們加速接口的訪問速度,減少了后端負(fù)載,同時保證功能的更新
緩存穿透
緩存系統(tǒng),按照KEY去查詢VALUE,當(dāng)KEY對應(yīng)的VALUE一定不存在的時候并對KEY并發(fā)請求量很大的時候,就會對后端造成很大的壓力。
(查詢一個必然不存在的數(shù)據(jù)。比如文章表,查詢一個不存在的id,每次都會訪問DB,如果有人惡意破壞,很可能直接對DB造成影響。)
由于緩存不命中,每次都要查詢持久層。從而失去緩存的意義。
解決方法:
1、緩存層緩存空值。
–緩存太多空值,占用更多空間。(優(yōu)化:給個空值過期時間)
–存儲層更新代碼了,緩存層還是空值。(優(yōu)化:后臺設(shè)置時主動刪除空值,并緩存把值進(jìn)去)
2、將數(shù)據(jù)庫中所有的查詢條件,放到布隆過濾器中。當(dāng)一個查詢請求來臨的時候,先經(jīng)過布隆過濾器進(jìn)行檢查,如果請求存在這個條件中,那么繼續(xù)執(zhí)行,如果不在,直接丟棄。
備注:
比如數(shù)據(jù)庫中有10000個條件,那么布隆過濾器的容量size設(shè)置的要稍微比10000大一些,比如12000.
對于誤判率的設(shè)置,根據(jù)實際項目,以及硬件設(shè)施來具體決定。但是一定不能設(shè)置為0,并且誤判率設(shè)置的越小,哈希函數(shù)跟數(shù)組長度都會更多跟更長,那么對硬件,內(nèi)存中間的要求就會相應(yīng)的高。
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.0001);
有了size跟誤判率,那么布隆過濾器就會產(chǎn)生相應(yīng)的哈希函數(shù)跟數(shù)組。
綜上:我們可以利用布隆過濾器,將redis緩存擊穿控制在一個可容忍的范圍內(nèi)。
緩存雪崩(緩存失效)
如果緩存集中在一段時間內(nèi)失效,發(fā)生大量的緩存穿透,所有的查詢都落在數(shù)據(jù)庫上,造成了緩存雪崩。
緩存層宕掉后,流量會像奔逃的野牛一樣,打向后端存儲
解決方法:
- 在緩存失效后,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待。
- 可以通過緩存reload機(jī)制,預(yù)先去更新緩存,再即將發(fā)生大并發(fā)訪問前手動觸發(fā)加載緩存
- 不同的key,設(shè)置不同的過期時間,讓緩存失效的時間點盡量均勻
- 做二級緩存,或者雙緩存策略。A1為原始緩存,A2為拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設(shè)置為短期,A2設(shè)置為長期。
熱點key
(1) 這個key是一個熱點key(例如一個重要的新聞,一個熱門的八卦新聞等等),所以這種key訪問量可能非常大。
(2) 緩存的構(gòu)建是需要一定時間的。(可能是一個復(fù)雜計算,例如復(fù)雜的sql、多次IO、多個依賴(各種接口)等等)
于是就會出現(xiàn)一個致命問題:在緩存失效的瞬間,有大量線程來構(gòu)建緩存(見下圖),造成后端負(fù)載加大,甚至可能會讓系統(tǒng)崩潰 。
解決方法:
1. 使用互斥鎖(mutex key):這種解決方案思路比較簡單,就是只讓一個線程構(gòu)建緩存,其他線程等待構(gòu)建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)就可以了
2. "提前"使用互斥鎖(mutex key):在value內(nèi)部設(shè)置1個超時值(timeout1), timeout1比實際的memcache timeout(timeout2)小。當(dāng)從cache讀取到timeout1發(fā)現(xiàn)它已經(jīng)過期時候,馬上延長timeout1并重新設(shè)置到cache。然后再從數(shù)據(jù)庫加載數(shù)據(jù)并設(shè)置到cache中。
3. "永遠(yuǎn)不過期":
這里的“永遠(yuǎn)不過期”包含兩層意思:
(1) 從redis上看,確實沒有設(shè)置過期時間,這就保證了,不會出現(xiàn)熱點key過期問題,也就是“物理”不過期。
(2) 從功能上看,如果不過期,那不就成靜態(tài)的了嗎?所以我們把過期時間存在key對應(yīng)的value里,如果發(fā)現(xiàn)要過期了,通過一個后臺的異步線程進(jìn)行緩存的構(gòu)建,也就是“邏輯”過期
4. 資源保護(hù):可以做資源的隔離保護(hù)主線程池,如果把這個應(yīng)用到緩存的構(gòu)建也未嘗不可。
四種方案對比:
作為一個并發(fā)量較大的互聯(lián)網(wǎng)應(yīng)用,我們的目標(biāo)有3個:
1. 加快用戶訪問速度,提高用戶體驗。
2. 降低后端負(fù)載,保證系統(tǒng)平穩(wěn)。
3. 保證數(shù)據(jù)“盡可能”及時更新(要不要完全一致,取決于業(yè)務(wù),而不是技術(shù)。)
所以第二節(jié)中提到的四種方法,可以做如下比較,還是那就話:沒有最好,只有最合適。
解決方案 | 優(yōu)點 | 缺點 |
簡單分布式鎖(Tim yang) |
1. 思路簡單 2. 保證一致性 |
1. 代碼復(fù)雜度增大 2. 存在死鎖的風(fēng)險 3. 存在線程池阻塞的風(fēng)險 |
加另外一個過期時間(Tim yang) | 1. 保證一致性 | 同上 |
不過期(本文) |
1. 異步構(gòu)建緩存,不會阻塞線程池 |
1. 不保證一致性。 2. 代碼復(fù)雜度增大(每個value都要維護(hù)一個timekey)。 3. 占用一定的內(nèi)存空間(每個value都要維護(hù)一個timekey)。 |
資源隔離組件hystrix(本文) |
1. hystrix技術(shù)成熟,有效保證后端。 2. hystrix監(jiān)控強(qiáng)大。 |
1. 部分訪問存在降級策略。 |
總結(jié)
1. 熱點key + 過期時間 + 復(fù)雜的構(gòu)建緩存過程 => mutex key問題
2. 構(gòu)建緩存一個線程做就可以了。
3. 四種解決方案:沒有最佳只有最合適。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java實現(xiàn)頁面多查詢條件必選的統(tǒng)一處理思路
這篇文章主要為大家介紹了java實現(xiàn)頁面多查詢條件必選的統(tǒng)一處理思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06SpringBoot整合Dubbo+Zookeeper實現(xiàn)RPC調(diào)用
這篇文章主要給大家介紹了Spring Boot整合Dubbo+Zookeeper實現(xiàn)RPC調(diào)用的步驟詳解,文中有詳細(xì)的代碼示例,對我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-07-07springmvc實現(xiàn)導(dǎo)出數(shù)據(jù)信息為excle表格示例代碼
本篇文章主要介紹了springmvc實現(xiàn)導(dǎo)出數(shù)據(jù)信息為excle表格,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧。2017-01-01java實現(xiàn)建造者模式(Builder Pattern)
這篇文章主要為大家詳細(xì)介紹了java實現(xiàn)建造者模式Builder Pattern,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-10-10使用spring框架ResponseEntity實現(xiàn)文件下載
這篇文章主要介紹了使用spring框架ResponseEntity實現(xiàn)文件下載,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02SpringBoot后端數(shù)據(jù)校驗實戰(zhàn)操作指南
在項?開發(fā)中,對于前端提交的表單,后臺接?接收到表單數(shù)據(jù)后,為了保證程序的嚴(yán)謹(jǐn)性,通常后端會加?業(yè)務(wù)參數(shù)的合法校驗操作來避免程序的?技術(shù)性?bug,這篇文章主要給大家介紹了關(guān)于SpringBoot后端數(shù)據(jù)校驗的相關(guān)資料,需要的朋友可以參考下2022-07-07