性能優化之緩存篇

1. 前言:爲何要用緩存?

用戶數增加,架構演變,數據量增大,開始考慮怎麼去作性能優化。html

而性能優化的第必定律就是:優先考慮使用緩存。前端

2. 緩存的基本原理

2.1 緩存的做用

一、加快數據訪問速度;java

二、減輕後端應用和數據存儲的負載壓力。redis

2.2 緩存的特徵

一、命中率:命中率 = 命中數 / 請求數。算法

這是衡量緩存有效性的重要指標。命中率越高,代表緩存的使用率越高。spring

二、最大元素(最大空間)。數據庫

一旦緩存中元素數量超過這個值(或者緩存數據空間超過其最大支 持空間),將會觸發淘汰策略後端

三、淘汰策略。瀏覽器

這個我前文其實已經說過。緩存

FIFO(First In First Out) 先進先出,淘汰最先數據。

判斷存儲時間,離目前最遠的數據優先淘汰。

LRU (Least Recently Used)剔除最近最少使用。

判斷最近使用時間,離目前最遠的數據優先淘汰。

LFU (Least Frequently Used)剔除最近使用頻率最低的數據。

在一段時間內,數據被使用次數最少的,優先淘汰。

具體能夠看這篇文章常見的緩存剔除策略 & LRU與LFU的區別

3. 緩存的分類

緩存的主要手段有:瀏覽器緩存、CDN、反向代理、本地緩存、分佈式緩存、數據庫緩存。

解讀《大型網站技術架構》一文中,其實已經說到過。

咱們通常說作性能優化時是指後三個:本地緩存、分佈式緩存、數據庫緩存。

前面三個緩存策略屬於網站前端的範疇。

從硬件介質上來看,緩存分爲內存和硬盤兩種。

但從技術上,又能夠分紅內存、硬盤文件、數據庫。

咱們一般意義上說的緩存通常都是基於內存的。

由於只有內存,才足夠快。

數據庫緩存通常也是基於內存的,但這個活通常是DBA在配置數據庫的時候就設置好了。

對於大部分開發人員來講,咱們通常所說的緩存優化都是基於本地緩存(ocal cache)和遠程緩存(remote cache)。

而如今遠程緩存這個詞通常也被分佈式緩存這個經常使用方案所代指。

4. 何時使用緩存?

4.1 緩存的使用判斷

何時使用緩存的判斷其實比較簡單,抓住兩點就好了:

一、是否是熱點數據?

所謂熱點,通常是遵循二八定律,即百分之八十的訪問集中在百分之二十的數據上。

二、是否是讀比寫多?

這個比例通常爲2:1。

4.2 何時不該該使用緩存?

反過來就是了。

一、沒有熱點數據不要使用緩存,也沒什麼意義。

由於內存資源是比較寶貴的。

二、頻繁修改的數據不要使用緩存。

由於可能寫入後還來不及讀取就已失效或被淘汰,而且容易產生髒讀。

4.3 合理使用緩存

最後,最重要的是確認是否須要使用緩存?

肯定了後,再選擇合適的緩存工具及使用緩存的方式。

5. 緩存時常見的一些問題

使用緩存優勢不少,但也存在一些很常見的問題。雙刃之劍,就看怎麼用了。

列舉一些咱們工做中常見的一些緩存問題,並給出至少一種解決方案。

5.1 緩存更新帶來的數據不一致與髒讀

緩存更新的常見策略有:

一、先更新數據庫再更新緩存;

二、先更新數據庫再刪除緩存;

三、先刪除緩存再更新數據庫;

四、定時清理緩存;

五、有請求訪問數據時,判斷緩存是否過時,過時從數據庫中刷新緩存。

在這幾種方案中,若是修改緩存與數據庫不在同一個事物中,就帶來了數據不一致和髒讀的問題。

對應方案1:先刪除緩存再更新數據庫,而且在同一個事物中。

對應方案2:緩存自動失效後,另外的異步線程進行緩存更新。

對應方案3:緩存更新在併發、分佈式要考慮鎖,redis天生就是單線程,比較有優點。

5.2 怎麼作緩存預熱

緩存預熱是指在用戶可訪問服務以前,將熱點數據加載到緩存的操做,這樣能夠有效避免上線後瞬時大流量形成系統不可用。

緩存預熱的通常性策略:

一、開發個緩存刷新功能,手工刷新;

二、項目啓動的時候自動進行加載(通常爲字典表等數據量不大的數據);

三、設置個定時器,自動刷新緩存;

四、提早統計熱點數據,事先批量加載到如redis這樣緩存工具中。

5.3 緩存重建

緩存失效後,重建熱點緩存,若是耗時較長,在重建過程當中,性能、負載很差。

對應方案:

一、正常狀況下,交錯緩存失效時間,減輕緩存壓力;

二、崩潰失效的狀況下,可使用帶持久化功能的緩存來恢復,好比Redis;

三、若是是MongoDB則不太同樣,它是採用mmap來將數據文件映射到內存中,因此當MongoDB重啓時,這些映射的內存並不會清掉,不須要進行緩存重建與預熱。

5.4 緩存雪崩與可用性

緩存雪崩:緩存在同一時間失效時,訪問直達數據庫層,可能致使DB掛掉、系統崩潰。

對應方案1:交錯緩存失效時間或隨機緩存失效時間。

對應方案2:主從熱備(Redis Sentinel)。

對應方案3:集羣/水平切分(Redis Cluster、一致性哈希)。

5.5 緩存穿透

緩存穿透:持續高併發訪問某個不存在的Key。

對應方案1:空值緩存。

對應方案2:布隆過濾器(bloom filter) + bitmap。窮舉可能訪問的數據放入bitmap中,使用hash訪問。

5.6 緩存擊穿

緩存擊穿:熱點Key失效,高併發請求,直擊數據庫。

緩存擊穿與緩存穿透很類似,不一樣點是是緩存擊穿前訪問的是真實的熱點數據,只是在某一剎那失效了,形成了擊穿的效果。

這樣看,它其實也是緩存雪崩的一個特例。與雪崩的區別即在於擊穿是對於特定的熱點數據,而雪崩是所有數據。

對應方案:多級緩存及交錯失效時間 + LRU 淘汰算法。

對於熱點數據進行二級或多級緩存,並對於不一樣級別的緩存設定不一樣的失效時間,緩解雪崩。

此外可以使用LRU的變種算法LRU-K緩存數據。

5.7 緩存降級

緩存降級是服務降級中的一環。

在訪問量劇增,致使服務出現問題時,爲了保證核心服務可用,防止發生緩存雪崩,可進行服務降級。

以redis爲例,比較常見的作法就是,不去數據庫查詢,而是直接返回默認值給用戶。

緩存降級也可根據日誌級別進行預案設置。

6. 分佈式緩存的選型

說了這麼多緩存的原理與策略,說說咱們在實際工做中應該怎麼去作緩存選型。

如下就是經常使用的幾種緩存工具。

6.1 Ehcache

Ehcache是純Java開源的緩存框架,最先從hibernate發展而來,如今算是springboot中的官配緩存工具,整合簡單。特色以下:

  • 快速,針對大型高併發系統場景,Ehcache的多線程機制有相應的優化改善;

  • 簡單,很小的jar包,簡單配置就可直接使用,單機場景下無需過多的其餘服務依賴;

  • 支持多種的緩存策略,靈活;

  • 緩存數據有兩級:內存和磁盤,與通常的本地內存緩存相比,有了磁盤的存儲空間,將能夠支持更大量的數據緩存需求;

  • 具備緩存和緩存管理器的偵聽接口,能更簡單方便的進行緩存實例的監控管理;

  • 支持多緩存管理器實例,以及一個實例的多個緩存區域。

6.2 Guava Cache

Guava Cache是Google開源的Java重用工具集庫Guava裏的一款緩存工具,特色以下:

  • 自動將entry節點加載進緩存結構中;

  • 當緩存的數據超過設置的最大值時,使用LRU算法移除;

  • 具有根據entry節點上次被訪問或者寫入時間計算它的過時機制;

  • 緩存的key被封裝在WeakReference引用內;

  • 緩存的Value被封裝在WeakReference或SoftReference引用內;

  • 統計緩存使用過程當中命中率、異常率、未命中率等統計數據。

6.3 Memcache

memcache自己不支持分佈式,是經過客戶端的路由處理來達到分佈式解決方案的目的。特色以下:

  • memcache使用預分配內存池的方式管理內存;

  • 全部數據存儲在物理內存裏;

  • 非阻塞IO複用模型,純KV存取操做;

  • 多線程,效率高,會遇到鎖等上下文切換問題;

  • 只支持簡單KV數據類型;

  • 數據不支持持久化。

6.4 Redis

Redis是當前主流的高性能內存數據庫,多用於存儲緩存數據,並能實現輕量級的MQ功能。特色以下:

  • 臨時申請空間,可能致使碎片;

  • 有VM機制,能存儲更多數據,超過內存空間後會致使swap,下降效率;

  • 非阻塞IO複用模型,支持額外CPU計算:排序、聚合,會影響IO性能;

  • 單線程,無鎖,無上下文切換,單實例沒法利用多核性能;

  • 支持多種數據類型:string / hash / list / set / sorted set;

  • 數據支持持久化:AOF(語句增量)/RDB(fork全量);

  • 自然支持高可用分佈式方案sentinel +;

  • cluster(故障自動轉移+集羣)。

6.5 推薦

一般狀況下,單機咱們會用Ehcache,甚至java本身的concurrenthashmap來實現緩存。

分佈式通常選擇redis。

07 附錄:參考資料

緩存那些事 | 美團明輝

什麼是緩存預熱,緩存更新,緩存降級的概念?

相關文章
相關標籤/搜索