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