深入淺出分布式系統(tǒng)中的緩存架構(gòu)
大家好, 我是「老黑」。
緩存,已經(jīng)是一個(gè)老生常談的技術(shù)了,在高并發(fā)讀的情況下對(duì)于讀服務(wù)來(lái)說可謂是抗流量的銀彈。
高并發(fā)三大利器:緩存、限流、降級(jí)。
今天我們就來(lái)談?wù)劸彺?。「?duì)于緩存,我的理解是讓數(shù)據(jù)更接近于用戶,目的是讓用戶的訪問速度更快?!? 所以距離越接近用戶的緩存,越快越有效!緩存的工作原理是先從緩存中獲取數(shù)據(jù),如果有數(shù)據(jù)則直接返回給用戶,如果沒有數(shù)據(jù)則從慢速設(shè)備上讀取實(shí)際數(shù)據(jù)并且將數(shù)據(jù)放入緩存。
按照層級(jí)關(guān)系,我們來(lái)劃分一下緩存,同時(shí)也是我們今天的「大綱」:
瀏覽器緩存
瀏覽器是我們網(wǎng)上沖浪的重要工具,為了能夠讓我們順暢的沖浪,它也會(huì)幫助我們緩存一些東西,主要存放一些實(shí)時(shí)性不太敏感的數(shù)據(jù),比如商品詳情頁(yè)框架、商家評(píng)分、評(píng)價(jià)、廣告詞等。對(duì)于實(shí)時(shí)性要求高的數(shù)據(jù)則不能使用瀏覽器緩存。瀏覽器緩存是有過期時(shí)間的,我們可以通過對(duì)響應(yīng)頭Expires、Cache-control進(jìn)行控制。
客戶端緩存
客戶端緩存很容易理解,意思就是存放在客戶端的緩存。它的使用場(chǎng)景不多,在我們大促的時(shí)候,為了防止瞬間流量把服務(wù)端擊垮,一般會(huì)在大促來(lái)臨之前把a(bǔ)pp需要訪問的一些素材(如js/css/image等)提前下發(fā)到客戶端進(jìn)行緩存,在大促來(lái)臨之際app就不需要去拉取這些素材了。另外的話還有一些兜底數(shù)據(jù)或者樣式文件也會(huì)存放于客戶端緩存中,在服務(wù)端異?;蛘呔W(wǎng)絡(luò)異常的時(shí)候保證app不崩。
CDN緩存
CDN(Content Delivery Network),即內(nèi)容分發(fā)網(wǎng)絡(luò)。它是建立并覆蓋在承載網(wǎng)之上,由分布在不同區(qū)域的邊緣節(jié)點(diǎn)服務(wù)器群組成的分布式網(wǎng)絡(luò)。我們通常會(huì)將一些靜態(tài)頁(yè)面數(shù)據(jù)、活動(dòng)頁(yè)面、圖片等數(shù)據(jù)存放于CDN緩存中。
CDN緩存有兩種機(jī)制:推送機(jī)制(當(dāng)內(nèi)容變更后主動(dòng)將數(shù)據(jù)推送到CDN節(jié)點(diǎn))和拉取機(jī)制(先訪問CDN節(jié)點(diǎn),無(wú)數(shù)據(jù)的時(shí)候會(huì)從源服務(wù)器獲取數(shù)據(jù)返回并存儲(chǔ)CDN節(jié)點(diǎn))。
舉個(gè)例子,如果你要去買汽車,你應(yīng)該是到4s店去買汽車,如果4s店有你可以直接提走,如果4s店沒有,那么4s店鋪需要去進(jìn)一批貨,然后回到店鋪,然后再給你。在這個(gè)case中,4s店其實(shí)就承當(dāng)了一個(gè)CDN緩存節(jié)點(diǎn)的角色。
反向代理緩存
反向代理,我們一般情況都是指反向代理服務(wù)器Nginx。
Nginx緩存主要分為Nginx Http緩存與Nginx代理層緩存。
Nginx Http緩存提供expires、etag、if-modified-since指令來(lái)實(shí)現(xiàn)反向代理緩存。Nginx代理層緩存主要以Http模塊與proxy_cacahe模塊進(jìn)行配置即可。
本地緩存
本地緩存,一般是指將客戶機(jī)本地的物理內(nèi)存劃分出一部分空間用來(lái)緩沖客戶機(jī)回寫到服務(wù)器的數(shù)據(jù)。從全局的角度,我們可以有「磁盤緩存」、「CPU緩存」、「應(yīng)用緩存」。
「磁盤緩存」分為讀緩存和寫緩存。
讀緩存是指,操作系統(tǒng)為已讀取的文件數(shù)據(jù),在內(nèi)存較空閑的情況下留在內(nèi)存空間中(這個(gè)內(nèi)存空間被稱之為“內(nèi)存池”),當(dāng)下次軟件或用戶再次讀取同一文件時(shí)就不必重新從磁盤上讀取,從而提高速度。
寫緩存實(shí)際上就是將要寫入磁盤的數(shù)據(jù)先保存于系統(tǒng)為寫緩存分配的內(nèi)存空間中,當(dāng)保存到內(nèi)存池中的數(shù)據(jù)達(dá)到一個(gè)程度時(shí),便將數(shù)據(jù)保存到硬盤中。
「CPU緩存」可以分為一級(jí)緩存(L1 Cache)、二級(jí)三級(jí)緩存(L2/L3)。當(dāng)CPU要讀取一個(gè)數(shù)據(jù)時(shí),首先從L1中查找,沒有的話再?gòu)腖2/L3中查找,如果還沒有那就從內(nèi)存中查找,內(nèi)存如果還沒有那就從磁盤查找。查找順序?yàn)椋篊PU->L1->L2/L3->內(nèi)存->磁盤。
「應(yīng)用緩存」分為本地應(yīng)用緩存與其他應(yīng)用緩存。
本地應(yīng)用緩存指的是本服務(wù)所使用的緩存,用Java服務(wù)來(lái)舉例,又分為 堆內(nèi)緩存 與 堆外緩存 。
堆內(nèi)緩存,一般指的是Java堆的緩存對(duì)象,堆內(nèi)緩存的好處是不需要序列化/反序列化,也是最快的緩存,缺點(diǎn)也很明顯,緩存數(shù)據(jù)多的時(shí)候,GC(垃圾回收)的頻率會(huì)增大,時(shí)間會(huì)加長(zhǎng)。堆內(nèi)緩存一般使用軟引用/弱引用來(lái)引用對(duì)象,使用這兩種引用的好處是當(dāng)堆內(nèi)存不足時(shí),可以強(qiáng)制回收這部分內(nèi)存,釋放堆空間。堆內(nèi)緩存最大的問題是重啟時(shí)內(nèi)存中的緩存數(shù)據(jù)會(huì)丟失,如果堆內(nèi)緩存使用的多,再加上剛好流量風(fēng)暴,有可能擊垮應(yīng)用。堆內(nèi)緩存的實(shí)現(xiàn)一般有:Guava Cache、Ehcache等。
堆外緩存,這個(gè)聽說的同學(xué)比較少,它處于Java堆之外的內(nèi)存,不受GC控制,也不受限堆大小,只受限于機(jī)器內(nèi)存,所以,使用它一定小心謹(jǐn)慎,如果處理不當(dāng)它可能存在內(nèi)存泄漏的風(fēng)險(xiǎn)!堆外內(nèi)存需要序列化/反序列化,所以它會(huì)比堆內(nèi)緩存慢一些。
其他應(yīng)用緩存,指的是除了本服務(wù)之外的緩存,比如local redis cache。local redis cache指的是在本服務(wù)器上部署一組Redis,應(yīng)用直接讀本機(jī)獲取緩存數(shù)據(jù),多機(jī)之間利用主從機(jī)制同步數(shù)據(jù)。這種方式的優(yōu)點(diǎn)是沒有網(wǎng)絡(luò)消耗,性能是最優(yōu)的。
分布式緩存
如果數(shù)據(jù)量不大的情況下,使用local redis cache的架構(gòu)是最優(yōu)的。
使用local redis cache最大的問題是:
- 單機(jī)器容量問題
- 多實(shí)例數(shù)據(jù)一致性問題
- 多實(shí)例緩存命中率降低導(dǎo)致回源DB
如果遇到這樣的問題,那么應(yīng)該將數(shù)據(jù)分片,盡可能的均勻分布到多臺(tái)服務(wù)器,這便是分布式緩存。
分布式緩存常見的分片策略有:
- 節(jié)點(diǎn)取余
- 一致性哈希
- 虛擬槽分區(qū)
我們最常見的Redis-Cluster集群則是使用虛擬槽分區(qū)的方式來(lái)對(duì)數(shù)據(jù)分片的。
我們點(diǎn)到即止,對(duì)于Redis緩存相關(guān),后面會(huì)有很多文章來(lái)專門討論,敬請(qǐng)期待吧!
其他:緩存命中率
緩存命中率是我們非常重要的一個(gè)指標(biāo),我們?nèi)绻褂镁彺妫欢ㄐ枰ㄟ^監(jiān)控這個(gè)指標(biāo)來(lái)看緩存的工作狀態(tài)。
它的計(jì)算方式為:
命中率緩存命中次數(shù)讀取總次數(shù)緩存命中率越高越好,如何提高緩存命中率呢?我們應(yīng)該對(duì)于不同場(chǎng)景數(shù)據(jù)有不同的緩存策略,比如:
- 大促來(lái)臨之際應(yīng)該提前將熱點(diǎn)數(shù)據(jù)緩存,這種方式我們稱之為緩存預(yù)熱或緩存熱加載;
- 在case1的基礎(chǔ)上,將熱點(diǎn)緩存數(shù)據(jù)與普通緩存數(shù)據(jù)做數(shù)據(jù)隔離,這一點(diǎn)前期需要人為干預(yù),后期需要實(shí)時(shí)熱點(diǎn)發(fā)現(xiàn);
- 將數(shù)據(jù)分類,不同類別的數(shù)據(jù)配置合適的失效時(shí)間;
- 調(diào)整緩存粒度,通常情況下緩存粒度越小緩存命中率越高;
- 增大存儲(chǔ)容量,當(dāng)容量不夠的時(shí)候會(huì)觸發(fā)過期策略導(dǎo)致部分緩存數(shù)據(jù)失效,從而影響緩存命中率;
緩存問題:緩存擊穿
[一句話概述]緩存擊穿是指數(shù)據(jù)庫(kù)和緩存都沒有的數(shù)據(jù),每次都要經(jīng)過緩存去訪問數(shù)據(jù)庫(kù),大量的請(qǐng)求有可能導(dǎo)致DB宕機(jī)。(強(qiáng)調(diào)都沒有數(shù)據(jù)+并發(fā)訪問)
這里我繼續(xù)點(diǎn)到即止,后續(xù)奉上,敬請(qǐng)期待。
緩存問題:緩存穿透
[一句話概述]緩存擊穿是指數(shù)據(jù)庫(kù)有,緩存沒有的數(shù)據(jù),大量請(qǐng)求訪問這個(gè)緩存不存在的數(shù)據(jù),最后請(qǐng)求打到DB可能導(dǎo)致DB宕機(jī)。(強(qiáng)調(diào)單個(gè)Key過期+并發(fā)訪問)
這里我繼續(xù)點(diǎn)到即止,后續(xù)奉上,敬請(qǐng)期待。
緩存問題:緩存雪崩
[一句話概述]緩存擊穿是指數(shù)據(jù)庫(kù)有,緩存沒有的數(shù)據(jù),大量請(qǐng)求訪問這些緩存不存在的數(shù)據(jù),最后請(qǐng)求打到DB可能導(dǎo)致DB宕機(jī)。(強(qiáng)調(diào)批量Key過期+并發(fā)訪問)
這里我繼續(xù)點(diǎn)到即止,后續(xù)奉上,敬請(qǐng)期待。
緩存問題:緩存一致性
[一句話概述]緩存一致性指的是緩存與DB之間的數(shù)據(jù)一致性,我們需要通過各種手段來(lái)防止緩存與DB不一致,我們要保證緩存與DB的數(shù)據(jù)一致或者數(shù)據(jù)最終一致。
這里我繼續(xù)點(diǎn)到即止,后續(xù)奉上,敬請(qǐng)期待。
緩存的其他問題
緩存的好處我們非常受益,用戶的每一次請(qǐng)求都伴隨著無(wú)數(shù)緩存的誕生,但是緩存同時(shí)也給我們帶來(lái)了不小的挑戰(zhàn),比如在上面提到的一些疑難課題:緩存穿透、緩存擊穿、緩存雪崩和緩存一致性。
除此之外,我們還會(huì)涉及到其他的一些緩存難題,如:緩存傾斜、緩存阻塞、緩存慢查詢、緩存主從一致性問題、緩存高可用、緩存故障發(fā)現(xiàn)與故障恢復(fù)、集群擴(kuò)容收縮、大Key熱Key......
我們今天只做一個(gè)緩存的開篇,具體的細(xì)節(jié),留給我們后續(xù)的章節(jié)中吧。