解析高性能進程緩存-caffeine

1.簡介

? ? 對于用戶來說矾利,響應(yīng)的快慢是判斷一個系統(tǒng)的重要指標(biāo)璧榄,緩存就是必不可少的優(yōu)化工具,在一個高并發(fā)的場景中往往占有著非常重要的角色,所以開發(fā)人員需要根據(jù)不同的應(yīng)用場景來選擇不同的緩存框架芦昔,比如分布式緩存redis,或者進程緩存GuavaCache胖腾。

? ? 進程緩存與Map之間的本質(zhì)區(qū)別就是能自動的回收存儲的元素烟零,而GuavaCache是一款非常優(yōu)秀的進程緩存框架,很好的提供了讀寫和自動失效的功能咸作。而今天要介紹的進程緩存Caffeine锨阿,在設(shè)計上參考了GuavaCache的經(jīng)驗,也進行了大量的改進優(yōu)化记罚,以下數(shù)據(jù)圖片均來源于Caffeine GitHub地址:caffeine墅诡,首先是讀寫性能的比較:

8個線程同時從緩存中讀取


8個線程同時從緩存中寫入


6個線程讀取宝剖,2個線程寫入

可以看到caffeine在讀寫方面明顯優(yōu)與其他框架蚌卤,在緩存命中率上Caffeine也不同于Guava,采用了更為優(yōu)秀的Window TinyLfu算法涛浙,該算法是在LRU的基礎(chǔ)上改進的版本说庭。


2.填充策略

(1)手動填充

手動填充

? ? newBuilder方法只是Caffeine類的一個空的構(gòu)造函數(shù)然磷,類屬性的實例化是在build方法中進行的,put方法就是手動填充緩存刊驴。newBuilder方法后面還能跟很多配置方法姿搜,比如

我們也可以使用 get 方法獲取值,該方法將一個參數(shù)為 key 的 Function 作為參數(shù)傳入捆憎。如果緩存中不存在該 key舅柜,則該函數(shù)將用于提供默認(rèn)值,該值在計算后插入緩存中躲惰。
Caffeine類是Caffeine的基礎(chǔ)類致份,里面提供了很多配置方法和參數(shù):

maximumSize:設(shè)置緩存最大條目數(shù),超過條目則觸發(fā)回收础拨。?
maximumWeight:設(shè)置緩存最大權(quán)重氮块,設(shè)置權(quán)重是通過weigher方法, 需要注意的是權(quán)重也是限制緩存大小的參數(shù)诡宗,并不會影響緩存淘汰策略滔蝉,也不能和maximumSize方法一起使用。?
weakKeys:將key設(shè)置為弱引用僚焦,在GC時可以直接淘汰
weakValues:將value設(shè)置為弱引用,在GC時可以直接淘汰
softValues:將value設(shè)置為軟引用曙痘,在內(nèi)存溢出前可以直接淘汰
expireAfterWrite:寫入后隔段時間過期
expireAfterAccess:訪問后隔斷時間過期
refreshAfterWrite:寫入后隔斷時間刷新
removalListener:緩存淘汰監(jiān)聽器芳悲,配置監(jiān)聽器后立肘,每個條目淘汰時都會調(diào)用該監(jiān)聽器
writer:writer監(jiān)聽器其實提供了兩個監(jiān)聽,一個是緩存寫入或更新是的write名扛,一個是緩存淘汰時的delete谅年,每個條目淘汰時都會調(diào)用該監(jiān)聽器

手動填充表示任何數(shù)據(jù)都需要手動put到cache中,沒有任何自動加載策略肮韧。put方法會覆蓋相同key的條目

(2)同步填充? ?

同步填充

通過在build方法中傳入一個CacheLoader的實現(xiàn)來進行同步填充融蹂,CacheLoader中的load方法制定了對key的計算,也可以重寫loadAll來進行批量計算弄企。

還有種方法是通過在build方法中傳入一個參數(shù)為 key 的 Function來進行同步填充超燃,這種方法類似于手動填充中的get方法。

(3)異步填充

異步填充

異步填充于同步填充大致相似拘领,區(qū)別是傳入一個執(zhí)行器進行異步執(zhí)行意乓,并且返回一個CompletableFuture對象,可以通過CompletableFuture.get來獲取數(shù)據(jù)并設(shè)置超時時間约素。


3.回收策略

? ? 條目的自動淘汰回收是map于cache最大的區(qū)別届良,Caffeine同樣包含了3中緩存回收機制,分別是基于大小圣猎,基于時間士葫,基于引用類型。

(1)基于大小

基于大小

? ? 設(shè)置了maximumSize屬性大小為1送悔,cache實例化是緩存size為0慢显,執(zhí)行了第一個put方法后緩存到達(dá)上限,第二個put執(zhí)行后會回收第一個緩存放祟。調(diào)用cleanUp方法是因為緩存回收是異步執(zhí)行鳍怨,cleanUp可以等待異步執(zhí)行完成。

基于權(quán)重
執(zhí)行結(jié)果

除了設(shè)置maximumSize外跪妥,設(shè)置maximumWeight也可以進行基于大小的緩存回收鞋喇,weigher簡單的設(shè)定了每個條目的權(quán)重為5,進行2次put后權(quán)重達(dá)到上限眉撵,所以第三次put執(zhí)行時會進行回收侦香。

(2)基于時間

基于時間

基于時間的方式主要是三種配置:
? ? expireAfterWrite:上次寫入后開始計時
? ? expireAfterAccess:上次訪問后開始計時,包括讀和寫
? ? expireAfter:自定義的時間計時器

(3)基于引用


基于引用

我們可以顯式的定義key或value為弱引用纽疟,或者value單獨定義為軟引用罐韩,這樣就會啟用基于引用的回收策略了,主要用到Java的GC進行回收污朽。
? ? 軟引用:在內(nèi)存溢出前回收
? ? 弱引用:在下次GC時回收
使用到的回收策略時LRU算法

RemovalCause

RemovalCause是一個enum散吵,記錄了緩存失效的原因,并且通過wasEvicted方法定義是否是自動淘汰。
EXPLICIT? ? //手動調(diào)用invalidate或remove等方法
REPLACED? ? ? ? //調(diào)用put等方法進行修改
COLLECTED? ? //設(shè)置了key或value的引用方式
EXPIRED? ? //設(shè)置了過期時間
SIZE? ? //設(shè)置了大小


4.刷新

cache除了會自動淘汰矾睦,也能進行自動刷新操作

自動刷新

refreshAfterWrite就是設(shè)置寫入后多就會刷新晦款,expireAfterWrite和refreshAfterWrite的區(qū)別是,當(dāng)緩存過期后枚冗,配置了expireAfterWrite缓溅,則調(diào)用時會阻塞,等待緩存計算完成赁温,返回新的值并進行緩存坛怪,refreshAfterWrite則是返回一個舊值,并異步計算新值并緩存股囊。


5.源碼解析

? ? 說完了基本的功能袜匿,接下來我們簡單的解析一下Caffeine內(nèi)部的實現(xiàn),因為Caffeine設(shè)計復(fù)雜毁涉,功能強大沉帮,所以本篇先進行粗力度的解析。如有錯誤歡迎指正贫堰。
? ? 首先我們看看在構(gòu)建cache的時候用來區(qū)分填充方式的build方法:

build

可以看到build方法都伴隨這一個三目運算符穆壕,并且最后會實例化兩個子類返回,buildAsync方法內(nèi)部也是這樣的實現(xiàn)其屏。那么這些實現(xiàn)類是干什么用的呢喇勋,我們先要明白Caffeine內(nèi)部接口的一個大致關(guān)系。

Cache

首先是Caffeine的Cache接口偎行,這個接口是Caffeine最底層的一個接口川背,主要提供了一些方法定義:

V getIfPresent(@Nonnull Object key);? ? ? ? ? ? ? ? ? ? //獲取緩存條目,不存在則返回NULL
V get(@Nonnull K key, @Nonnull Function<? super K, ? extends V> mappingFunction);? ? //獲取緩存條目蛤袒,不存在則執(zhí)行mappingFunction進行計算熄云,并存入緩存
Map<K, V> getAllPresent(@Nonnull Iterable<?> keys);? ? //批量獲取條目,返回一個Map
void put(@Nonnull K key, @Nonnull V value);? ? //插入一個條目到緩存中
void putAll(@Nonnull Map<? extends K,? extends V> map);? ? //批量緩存數(shù)據(jù)
void invalidate(@Nonnull Object key);? ? //回收一個條目
void invalidateAll(@Nonnull Iterable<?> keys);? ? 批量回收條目
void invalidateAll();? ? //回收全部條目
long estimatedSize();? ? //獲取緩存大小
CacheStats stats();? ? //獲取緩存狀態(tài)
ConcurrentMap<K, V> asMap();? ? //轉(zhuǎn)換為ConcurrentMap
void cleanUp();? ? //觸發(fā)清除緩存
Policy<K, V> policy();? ? //設(shè)定策略

LoadingCache

LoadingCache類繼承自Cache妙真,同時也定義了一些接口

V get(@Nonnull K key);? ? //獲取條目缴允,沒有function參數(shù),但是為空會調(diào)用CacheLoader的loadMap<K, V> getAll(@Nonnull Iterable<? extends K> keys);? ? //獲取條目珍德,為空會調(diào)用CacheLoader的loadAllvoid refresh(@Nonnull K key);? ? //會異步的通過CacheLoader的load更新緩存

可以看到Cache接口更像是Map练般,用來存放key-value,而LoadingCache定義了加載和更新的機制锈候,通過build方法中傳入的CacheLoader來操作條目薄料。

LocalManualCache

LocalManualCache也繼承自Cache,這個接口有兩個主要的實現(xiàn)類泵琳,就是上文提到的BoundedLocalManualCache和UnboundedLocalManualCache摄职。這些是實現(xiàn)類提供了Cache的具體實現(xiàn)誊役,并且UnboundedLocalManualCache也最低限度的提供了LocalCache的功能。而卻分使用這兩個實現(xiàn)的方式就是看我們是否配置了回收策略谷市。

UnboundedLocalManualCache

如果我們沒有配置任何的回收策略势木,則會默認(rèn)使用UnboundedLocalManualCache。

UnboundedLocalManualCache

? ? 該實現(xiàn)類最低限度的提供了緩存的功能歌懒,初始化時提供了一個默認(rèn)大小為16的ConcurrentHashMap用來存儲數(shù)據(jù),也提供了基本的狀態(tài)計數(shù)器溯壶,刪除監(jiān)聽器及皂,編寫器等。由于沒有任何主動的回收策略且改,UnboundedLocalManualCache的本質(zhì)就是對Map的操作验烧。

BoundedLocalManualCache

BoundedLocalManualCache是有回收策略的,所有Caffeine對于設(shè)置的每種回收策略都有一個對應(yīng)的實現(xiàn)類又跛,所以就有了LocalCacheFactory類來構(gòu)建響應(yīng)的實現(xiàn)類碍拆。

LocalCacheFactory

newBoundedLocalCache針對我們配置的每種情況都拼接了一個字符,最終得到一個對應(yīng)的實現(xiàn)類名慨蓝,這樣窮舉性的寫法也是因為Caffeine對每種情況都作出了優(yōu)化感混。

LocalCacheFactory的實現(xiàn)類

newBoundedLocalCache方法最后返回一個BoundedLocalCache,也是我們最終用到的實現(xiàn)類礼烈。


6.緩存過期策略解析

我們知道了Caffeine有三種過期策略弧满,接下來我們來大致分析下Caffeine是怎么主動的進行緩存回收的。從源碼中我們找到了這樣兩個方法:

讀后操作
寫后操作

這是在讀寫時分別調(diào)用的兩個方法此熬,進行一些讀寫的后續(xù)操作庭呜,其中都調(diào)用了一個scheduleDrainBuffers方法,這個方法就是用來進行過期任務(wù)調(diào)度的犀忱。

scheduleDrainBuffers

首先嘗試加鎖募谎,如果鎖失敗表明其他線程正在進行操作。鎖成功后會執(zhí)行drainBuffersTask阴汇,也就是Caffeine的PerformCleanupTask異步回收数冬。

PerformCleanupTask

PerformCleanupTask的performCleanUp方法會再次加鎖

maintenance

進到maintenance方法中,在這里我們看到很多的方法鲫寄,都是用來進行回收的吉执。
drainReadBuffer:讀緩存用盡
drainWriteBuffer:寫緩存用盡
drainKeyReferences:key引用隊列耗盡
drainValueReferences:value引用耗盡
expireEntries:達(dá)到過期時間
evictEntries:達(dá)到大小限制

獲取到當(dāng)前時間后對expireAfterAccess進行淘汰。

之后淘汰expireAfterWrite地来。

對于自定義時間通過時間輪來進行淘汰戳玫。


最后

? ? 本篇文章大致介紹了Caffeine的使用方法,填充策略未斑,回收策略以及粗略的進行了源碼的解析咕宿,Caffeine是一款非常優(yōu)秀的緩存框架,使用的設(shè)計理念和代碼實現(xiàn)都讓我受益良多,之后有機會我會繼續(xù)進行深入的理解和學(xué)習(xí)府阀,謝謝大家的瀏覽缆镣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市试浙,隨后出現(xiàn)的幾起案子董瞻,更是在濱河造成了極大的恐慌,老刑警劉巖田巴,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钠糊,死亡現(xiàn)場離奇詭異,居然都是意外死亡壹哺,警方通過查閱死者的電腦和手機抄伍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來管宵,“玉大人截珍,你說我怎么就攤上這事÷崞樱” “怎么了岗喉?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炸庞。 經(jīng)常有香客問我沈堡,道長,這世上最難降的妖魔是什么燕雁? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任诞丽,我火速辦了婚禮,結(jié)果婚禮上拐格,老公的妹妹穿的比我還像新娘僧免。我一直安慰自己,他們只是感情好捏浊,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布懂衩。 她就那樣靜靜地躺著,像睡著了一般金踪。 火紅的嫁衣襯著肌膚如雪浊洞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天胡岔,我揣著相機與錄音法希,去河邊找鬼。 笑死靶瘸,一個胖子當(dāng)著我的面吹牛苫亦,可吹牛的內(nèi)容都是我干的毛肋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼屋剑,長吁一口氣:“原來是場噩夢啊……” “哼润匙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唉匾,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤孕讳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后巍膘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卫病,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年典徘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片益咬。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡逮诲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幽告,到底是詐尸還是另有隱情梅鹦,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布冗锁,位于F島的核電站齐唆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏冻河。R本人自食惡果不足惜箍邮,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叨叙。 院中可真熱鬧锭弊,春花似錦、人聲如沸擂错。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钮呀。三九已至剑鞍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爽醋,已是汗流浹背蚁署。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚂四,地道東北人形用。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓就轧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親田度。 傳聞我的和親對象是個殘疾皇子妒御,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 簡介 在本文中,我們來看看 Caffeine — 一個高性能的 Java 緩存庫镇饺。 緩存和 Map 之間的一個根本...
    xiaolyuh閱讀 71,117評論 5 48
  • 原文:http://www.baeldung.com/java-caching-caffeine作者:baeldu...
    Oopsguy閱讀 11,044評論 0 6
  • 緩存和 Map 之間的一個根本區(qū)別在于緩存可以回收存儲的 item乎莉。回收策略為在指定時間刪除哪些對象奸笤。此策略直接影...
    tracy_668閱讀 11,735評論 4 6
  • 1. 前言 互聯(lián)網(wǎng)軟件神速發(fā)展惋啃,用戶的體驗度是判斷一個軟件好壞的重要原因,所以緩存就是必不可少的一個神器监右。在多線程...
    不知名的蛋撻閱讀 49,493評論 2 16
  • 大前天晚上放了兩任務(wù)到服務(wù)器上跑边灭,今天早上查看發(fā)現(xiàn)任務(wù)已經(jīng)算完了。起初還高興“怎么這次這么快”健盒,進去一看才發(fā)現(xiàn)媽蛋...
    lxt閱讀 411評論 6 1