一、引出熱點(diǎn)key問題
我們通常使用 緩存 + 過期時(shí)間的策略來幫助我們加速接口的訪問速度,減少了后端負(fù)載,同時(shí)保證功能的更新,一般情況下這種模式已經(jīng)基本滿足要求了。
但是有兩個(gè)問題如果同時(shí)出現(xiàn),可能就會(huì)對(duì)系統(tǒng)造成致命的危害:
(1) 這個(gè)key是一個(gè)熱點(diǎn)key(例如一個(gè)重要的新聞,一個(gè)熱門的八卦新聞等等),所以這種key訪問量可能非常大。
(2) 緩存的構(gòu)建是需要一定時(shí)間的。(可能是一個(gè)復(fù)雜計(jì)算,例如復(fù)雜的sql、多次IO、多個(gè)依賴(各種接口)等等)
于是就會(huì)出現(xiàn)一個(gè)致命問題:在緩存失效的瞬間,有大量線程來構(gòu)建緩存(見下圖),造成后端負(fù)載加大,甚至可能會(huì)讓系統(tǒng)崩潰 。
二、三種解決方案
我們的目標(biāo)是:盡量少的線程構(gòu)建緩存(甚至是一個(gè)) + 數(shù)據(jù)一致性 + 較少的潛在危險(xiǎn),下面會(huì)介紹四種方法來解決這個(gè)問題:
1、使用互斥鎖(mutex key): 這種解決方案思路比較簡(jiǎn)單,就是只讓一個(gè)線程構(gòu)建緩存,其他線程等待構(gòu)建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)就可以了(如下圖)
2、“提前”使用互斥鎖(mutex key)
在value內(nèi)部設(shè)置1個(gè)超時(shí)值(timeout1), timeout1比實(shí)際的memcache timeout(timeout2)小。當(dāng)從cache讀取到timeout1發(fā)現(xiàn)它已經(jīng)過期時(shí)候,馬上延長(zhǎng)timeout1并重新設(shè)置到cache。然后再從數(shù)據(jù)庫加載數(shù)據(jù)并設(shè)置到cache中。
3、 “永遠(yuǎn)不過期“
這里的“永遠(yuǎn)不過期”包含兩層意思:
(1) 從redis上看,確實(shí)沒有設(shè)置過期時(shí)間,這就保證了,不會(huì)出現(xiàn)熱點(diǎn)key過期問題,也就是“物理”不過期。
(2) 從功能上看,如果不過期,那不就成靜態(tài)的了嗎?所以我們把過期時(shí)間存在key對(duì)應(yīng)的value里,如果發(fā)現(xiàn)要過期了,通過一個(gè)后臺(tái)的異步線程進(jìn)行緩存的構(gòu)建,也就是“邏輯”過期
從實(shí)戰(zhàn)看,這種方法對(duì)于性能非常友好,唯一不足的就是構(gòu)建緩存時(shí)候,其余線程(非構(gòu)建緩存的線程)可能訪問的是老數(shù)據(jù),但是對(duì)于一般的互聯(lián)網(wǎng)功能來說這個(gè)還是可以忍受。