所謂多級緩存系統,就是指在一個系統的不一樣的架構層級進行數據緩存,以提高訪問效率。
node
咱們都知道,一個緩存系統,它面臨着許多問題,好比緩存擊穿,緩存穿透,緩存雪崩,緩存熱點等等問題,那麼,對於一個多級緩存系統,它有什麼問題呢?
算法
有以下幾個:
緩存熱點:多級緩存系統大多應用在高併發場景下,因此咱們須要解決熱點 Key 問題,如何探測熱點 Key?
數據一致性:各層緩存之間的數據一致性問題,如應用層緩存和分佈式緩存以前的數據一致性問題。
緩存過時:緩存數據能夠分爲兩大類,過時緩存和不過時緩存?如何設計,如何設計過時緩存?
數據庫
整個多級緩存系統被分爲三層:
應用層 Nginx 緩存
分佈式 Redis 緩存集羣
Tomcat 堆內緩存
緩存
整個架構流程以下:當接收到一個請求時,首先會分發到 Nginx 集羣中,這裏能夠採用 Nginx 的負載均衡算法分發給某一臺機器,使用輪詢能夠下降負載,或者採用一致性 Hash 算法來提高緩存命中率。
服務器
當 Nginx 層沒有緩存數據時,會繼續向下請求,在分佈式緩存集羣中查找數據,若是緩存命中,直接返回(而且寫入 Nginx 應用緩存中),若是未命中,則回源到 Tomcat 集羣中查詢堆內緩存。
網絡
在分佈式緩存中查詢不到數據,將會去 Tomcat 集羣中查詢堆內緩存,查詢成功直接返回(並寫入分 Redis 主集羣中),查詢失敗請求數據庫;堆內緩存。
架構
若是以上緩存中都沒有命中,則直接請求數據庫,返回結果,同步數據到分佈式緩存中。
併發
在簡單瞭解了多級緩存的基本架構以後,咱們就該思考如何解決上面提到的一系列問題。
負載均衡
緩存熱點
框架
緩存熱點,是一個很常見的問題,好比「某某明星宣佈結婚」等等,均可能產生大量請求訪問的問題,一個最麻煩也是最容易讓人忽視的事情就是如何探測到熱點 Key。
在緩存系統中,除了一些經常使用的熱點 Key 外,在某些特殊場合下也會出現大量的熱點 Key,咱們該如何發現呢?
有如下策略:
數據調研。能夠分析歷史數據以及針對不一樣的場合去預測出熱點 Key,這種方式雖然不能百分百使得緩存命中,可是倒是一種最簡單和節省成本的方案。
實時計算。可使用現有的實時計算框架,好比 Storm、Spark Streaming、Flink 等框架統計一個時間段內的請求量,從而判斷熱點 Key。或者也能夠本身實現定時任務去統計請求量。
這裏咱們着重討論一下第二種解決方案,對於熱點 Key 問題,當緩存系統中沒有發現緩存時,須要去數據庫中讀取數據。
當大量請求來的時候,一個請求獲取鎖去請求數據庫,其餘阻塞,接着所有去訪問緩存,這樣可能由於一臺服務器撐不住從而宕機。
好比正常一臺服務器併發量爲 5W 左右,產生熱點 Key 的時候達到了 10W 甚至 20W,這樣服務器確定會崩。因此咱們在發現熱點 Key 以後還須要作到如何自動負載均衡。
結合以上問題咱們從新設計架構,以下圖所示:
咱們將整個應用架構分爲:
應用層
分佈式緩存
系統層
數據層
在應用層,咱們採用 Nginx 集羣,而且對接實時計算鏈路,經過 Flume 監控 Nginx 日誌,將數據傳輸到 Kafka 集羣中,而後 Flink 集羣消費數據進行統計。
若是統計結果爲熱點 Key,則將數據寫入 Zookeeper 的節點中,而應用系統經過監控 Znode 節點,讀取熱點 Key 數據,去數據庫中加載數據到緩存中而且作到負載均衡。
實際上,對於應用系統中的每一臺服務器,還須要一層防禦機制,限流熔斷,這樣作的目的是爲了防止單臺機器請求量太高,使得服務器負載太高,不至於服務器宕機或者大量請求訪問數據庫。
簡單思路就是爲每一臺服務器設計一個閥值,當請求量大於該值就直接返回用戶空白頁面或者提示用戶幾秒後刷新從新訪問。
數據一致性
數據一致性問題主要體如今緩存更新的時候,如何更新緩存,保證數據庫與緩存以及各層緩存層之間的一致性。
對於緩存更新問題,先寫緩存仍是先寫數據庫,這裏省略若干字。以前的文章介紹過,有興趣的讀者能夠翻閱。
在單層緩存系統中,咱們能夠先採用刪除緩存而後更新數據庫的方案來解決其數據一致性問題,那麼對於多級緩存呢?
若是使用這種方案,咱們須要考慮,若是先刪除緩存,那麼須要逐層去作刪除操做,那麼這一系列操做對系統帶來的耗時也是很可觀的。
若是咱們使用分佈式事務機制,就須要考慮該不應將寫緩存放入事務當中,由於咱們更新分佈式緩存,須要走網絡通訊,大量的請求將致使網路抖動甚至阻塞,增長了系統的延遲,致使系統短期內不可用。
若是咱們不將寫緩存這一操做放入事務當中,那麼可能引發短期內數據不一致。
這也就是分佈式系統的 CAP 理論,咱們不能同時達到高可用和一致性。那麼該如何抉擇呢?
這裏咱們選擇保證系統的可用性,就一個秒殺系統來說,短暫的不一致性問題對用戶的體驗影響並不大(固然,這裏不涉及支付系統)。
而可用性對用戶來講卻很重要,一個活動可能在很短的時間內結束,而用戶須要在這段時間內搶到本身心儀的商品,因此可用性更重要一些(這裏須要根據具體場景進行權衡)。
在保證了系統的可用性的基礎上,咱們該如何實現呢?若是實時性要求不是很高,咱們能夠採用全量+增量同步的方式進行。
首先,咱們能夠按照預計的熱點 Key 對系統進行緩存預熱,全量同步數據到緩存系統。
接着,在須要更新緩存的時候,咱們能夠採用增量同步的方式更新緩存。好比咱們可使用阿里 Canal 框架同步 Binlog 的方式進行數據的同步。
緩存過時
緩存系統中的全部數據,根據數據的使用頻率以及場景,咱們能夠分爲過時 Key 以及不過時 Key,那麼對於過時緩存咱們該如何淘汰呢?
下面有經常使用的幾種方案:
FIFO:使用 FIFO 算法來淘汰過時緩存。
LFU:使用 LFU 算法來淘汰過時緩存。
LRU:使用 LRU 算法來淘汰過時緩存。
以上幾種方案是在緩存達到最大緩存大小的時候的淘汰策略,若是沒有達到最大緩存大小,咱們有下面幾種方式:定時刪除策略:設置一個定時任務,在規定時間內檢查而且刪除過時 Key。按期刪除策略:這種策略須要設置刪除的週期以及時長,如何設置,須要根據具體場合來計算。惰性刪除策略:在使用時檢查是否過時,若是過時直接去更新緩存,不然直接返回。