背景
公司專案中有用到caffeine cache 所以來了解一下,
本地快取也就是我們適用記憶體快取一些熱點資料,使應用程式的程式處理的更加的快,以及保護我們的一些有磁盤/網路IO操作的函式/方法,以達到減小我們服務的回應時間的目的,
對于java技術堆疊來講我們通常使用到的本地快取都有 一些原有的容器HashMap,google的guava cache等等,今天了解一下caffine快取,
簡介
Caffeine 是基于Java 8 開發的、提供了近乎最佳命中率的高性能本地快取組件,Spring5 開始不再支持 Guava Cache,改為使用 Caffeine,
我們先看一下github 官方:Caffeine 是一個高性能Java 快取庫,提供近乎最佳的命中率, 對于高性能我們很容易理解,最直接的理解就是快,然后支持的并發量很高,但是對于近乎最佳的命中率,為什么這么說呢?
官方檔案: https://maven-badges.herokuapp.com/maven-central/com.github.ben-manes.caffeine/caffeine
Maven centry :https://maven-badges.herokuapp.com/maven-central/com.github.ben-manes.caffeine/caffeine
Caffeine 原理
既然是快取,并且是快取到的是java行程中,對于java行程中,一般在jvm 動態運行時的存盤區域為我們的堆記憶體中,那這份資源是相當珍貴的,不可能讓快取資源導致我們的程式OOM,隨意本地快取一般都是有淘汰策略的,其實包括我們的JVM垃圾回識訓制,包括java的四種(強軟弱虛)參考型別都是為了節約我們珍貴的記憶體資源而有的,言回正傳們,那Caffeine cache的是通過什么淘汰演算法實作的呢?
快取淘汰策略
常見的快取策略
- FIFO(First In First Out):先進先出,
它是優先淘汰掉最先快取的資料、是最簡單的淘汰演算法,缺點是如果先快取的資料使用頻率比較高的話,那么該資料就不停地進進出出,因此它的快取命中率比較低,這也就是caffenie所描述的近乎最佳的命中率
- LRU(Least Recently Used):最近最久未使用,
它是優先淘汰掉最久未訪問到的資料,缺點是不能很好地應對偶然的突發流量,有一批資料在59秒都沒有被訪問,然后到1分鐘的時候過期了,但在1分01秒來了大批量的訪問,導致穿透快取,打垮資料庫,
- LFU(Least Frequently Used):
最近最少頻率使用,它是優先淘汰掉最不經常使用的資料,需要維護一個表示使用頻率的欄位, 就憑這個欄位對于快取來說就是很不友好的,因為我們的記憶體資源是真的很可貴的,
-
W-TinyLFU (WFLU)演算法
-
Caffeine 使用了 W-TinyLFU 演算法,解決了 LRU 和LFU上述的缺點,W-TinyLFU 演算法由論文《TinyLFU: A Highly Efficient Cache Admission Policy》提出,
-
解決第一個問題是采用了 Count–Min Sketch 演算法,降低頻率資訊帶來的記憶體消耗;
-
解決第二個問題是讓記錄盡量保持相對的“新鮮”(Freshness Mechanism),并且當有新的記錄插入時,可以讓它跟老的記錄進行“PK”,輸者就會被淘汰,這樣一些老的、不再需要的記錄就會被剔除,PK機制保障新上的熱點資料能夠快取,
1、統計頻率 Count–Min Sketch 演算法
Count-Min Sketch 是資料庫中用到的一種 Sketch,所謂 sketch 就是用很少的一點資料來描述全體資料的特性,犧牲了準確性但是代價變得很低,算是有點理解了,那這其實會讓我們想到 bloom Filter 他也就通過少量的資料描述一個相對存盤較大的資料特征,
bloom Filter 原理基本是一個陣列,而CM-Sketch 的內部資料結構是一個二維陣列count,寬度w,深度d,此外還需要d個兩兩獨立的哈希函式 ,更新的時候,用這些哈希函式算出 d個不同的哈希值,然后把對應的行的值加上c

如下圖快取訪問頻率存盤主要分為兩大部分,即 LRU 和 Segmented LRU ,新訪問的資料會進入第一個 LRU,在 Caffeine 里叫 WindowDeque,當 WindowDeque 滿時,會進入 Segmented LRU 中的 ProbationDeque,在后續被訪問到時,它會被提升到 ProtectedDeque,當 ProtectedDeque 滿時,會有資料降級到 ProbationDeque ,資料需要淘汰的時候,對 ProbationDeque 中的資料進行淘汰,具體淘汰機制:取ProbationDeque 中的隊首和隊尾進行 PK,隊首資料是最先進入佇列的,稱為受害者,隊尾的資料稱為攻擊者,比較兩者 頻率大小,大勝小汰,

總的來說,通過 reset 衰減,避免歷史熱點資料由于頻率值比較高一直淘汰不掉,并且通過對訪問佇列分成三段,這樣避免了新加入的熱點資料早早地被淘汰掉,那就是通過
高性能讀
在github上的簡介第一句話就是高性能讀寫,我們現在來看一下 高性能讀寫是如何實作的
Caffeine 認為讀操作是頻繁的,寫操作是偶爾的,讀寫都是異步執行緒更新頻率資訊,
讀緩沖
傳統的快取實作將會為每個操作加鎖,以便能夠安全的對每個訪問佇列的元素進行排序,一種優化方案是將每個操作按序加入到緩沖區中進行批處理操作,讀完把資料放到環形佇列 RingBuffer 中,為了減少讀并發,采用多個 RingBuffer,每個執行緒都有對應的 RingBuffer,環形佇列是一個定長陣列,提供高性能的能力并最大程度上減少了 GC所帶來的性能開銷,資料丟到佇列之后就回傳讀取結果,類似于資料庫的WAL機制,和ConcurrentHashMap 讀取資料相比,僅僅多了把資料放到佇列這一步,異步執行緒并發讀取 RingBuffer 陣列,更新訪問資訊,這邊的執行緒池使用的是下文實戰小節講的 Caffeine 配置引數中的 executor,

寫緩沖
與讀緩沖類似,寫緩沖是為了儲存寫事件,讀緩沖中的事件主要是為了優化驅逐策略的命中率,因此讀緩沖中的事件完整程度允許一定程度的有損,但是寫緩沖并不允許資料的丟失,因此其必須實作為一個安全的佇列,Caffeine 寫是把資料放入MpscGrowableArrayQueue 阻塞佇列中,它參考了JCTools里的MpscGrowableArrayQueue ,是針對 MPSC- 多生產者單消費者(Multi-Producer & Single-Consumer)場景的高性能實作,多個生產者同時并發地寫入佇列是執行緒安全的,但是同一時刻只允許一個消費者消費佇列,
Caffeine 實戰
配置引數
Caffeine 借鑒了Guava Cache 的設計思想,如果之前使用過 Guava Cache,那么Caffeine 很容易上手,只需要改變相應的類名就行,構造一個快取 Cache 示例代碼如下:
Cache cache = Caffeine.newBuilder().maximumSize(1000).expireAfterWrite(6, TimeUnit.MINUTES).softValues().build();Caffeine 類相當于建造者模式的 Builder 類,通過 Caffeine 類配置 Cache,配置一個Cache 有如下引數:
-
expireAfterWrite:寫入間隔多久淘汰;
-
expireAfterAccess:最后訪問后間隔多久淘汰;
-
refreshAfterWrite:寫入后間隔多久重繪,該重繪是基于訪問被動觸發的,支持異步重繪和同步重繪,如果和 expireAfterWrite 組合使用,能夠保證即使該快取訪問不到、也能在固定時間間隔后被淘汰,否則如果單獨使用容易造成OOM;
-
expireAfter:自定義淘汰策略,該策略下 Caffeine 通過時間輪演算法來實作不同key 的不同過期時間;
-
maximumSize:快取 key 的最大個數;
-
weakKeys:key設定為弱參考,在 GC 時可以直接淘汰;
-
weakValues:value設定為弱參考,在 GC 時可以直接淘汰;
-
softValues:value設定為軟參考,在記憶體溢位前可以直接淘汰;
-
executor:選擇自定義的執行緒池,默認的執行緒池實作是 ForkJoinPool.commonPool();
-
maximumWeight:設定快取最大權重;
-
weigher:設定具體key權重;
-
recordStats:快取的統計資料,比如命中率等;
-
removalListener:快取淘汰監聽器;
-
writer:快取寫入、更新、淘汰的監聽器,
總結
- Caffeine cache是一個本地快取,
- 相對于其他的本地快取,他有高性能和近乎完全命中的優勢
- 高性能來源于他的緩沖查和緩沖寫機制
- 高命中率來源于WLRU快取淘汰機制
- 以及演算法的原理和原油淘汰機制的劣勢,
參考
- https://zhuanlan.zhihu.com/p/348695568
- https://titanssword.github.io/2018-02-23-Bloom%20Filter%20and%20Count-Min%20Sketch.html
- https://nan01ab.github.io/2018/04/TinyLFU.html
- https://github.com/ben-manes/caffeine
-
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/290078.html
標籤:java
