作者:京東保險(xiǎn) 郭盼
1、簡介
小編最近在使用系統(tǒng)的時(shí)候,發(fā)現(xiàn)盡管應(yīng)用已經(jīng)使用了redis緩存提高查詢效率,但是仍然有進(jìn)一步優(yōu)化的空間,于是想到了比分布式緩存性能更好的本地緩存,因此對(duì)領(lǐng)域內(nèi)常用的本地緩存進(jìn)行了一番調(diào)研,有早期的Guava緩存、在Guava上進(jìn)一步傳承的Caffine以及自稱在Java中使用最廣泛的EhCache,那么我們?cè)撛趺催x擇適合自己應(yīng)用的緩存呢,小編下面會(huì)簡單介紹,并將以上緩存進(jìn)行一個(gè)對(duì)比,希望幫助大家選擇最適合自己系統(tǒng)的本地緩存。
2、Guava緩存簡介
Guava cache是Google開發(fā)的Guava工具包中一套完善的JVM本地緩存框架,底層實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)類似于ConcurrentHashMap,但是進(jìn)行了更多的能力拓展,包括緩存過期時(shí)間設(shè)置、緩存容量設(shè)置、多種淘汰策略、緩存監(jiān)控等,下面簡單介紹下這些功能及其使用方式。
2.1、緩存過期時(shí)間設(shè)置
Guava的過期時(shí)間設(shè)置有基于創(chuàng)建時(shí)間和最后一次訪問時(shí)間兩種策略.
(1) 基于創(chuàng)建時(shí)間
通過對(duì)比緩存記錄的插入時(shí)間來判斷,比如設(shè)置過期時(shí)間為5分鐘,不管中間有沒有訪問,到時(shí)過期。
public Cache createCache() { return CacheBuilder.newBuilder() .expireAfterWrite(5L, TimeUnit.MINUTES) .build(); }
(2) 基于過期時(shí)間
通過對(duì)比最近最后一次的訪問時(shí)間,比如設(shè)置5分鐘,每次訪問之后都會(huì)刷新過期時(shí)間為5分鐘,只有持續(xù)5分鐘沒有被訪問到才會(huì)過期。
public Cache createCache() { return CacheBuilder.newBuilder() .expireAfterAccess(5L, TimeUnit.MINUTES) .build(); }
2.2、緩存容量和淘汰策略設(shè)置
Guava cache是內(nèi)存型緩存,有內(nèi)存溢出風(fēng)險(xiǎn),因此需要設(shè)置緩存的最大存儲(chǔ)上限,通過緩存的條數(shù)或每條緩存的權(quán)重來判斷是否達(dá)到了設(shè)定閾值,當(dāng)緩存的數(shù)據(jù)量達(dá)到設(shè)定閾值之后,Guava cache支持使用FIFO和LRU的策略對(duì)緩存記錄采取淘汰的措施。
(1)限制緩存記錄條數(shù)
public Cache createCache() { return CacheBuilder.newBuilder() .maximumSize(100L) .build(); }
(2)限制緩存記錄權(quán)重
public Cache createCache() { return CacheBuilder.newBuilder() .maximumWeight(100L) .weigher((key, value) -> (int) Math.ceil(instrumentation.getObjectSize(value) / 1024L)) .build(); }
使用限制緩存記錄權(quán)重時(shí)要先計(jì)算weight的value對(duì)象的字節(jié)數(shù),每1kb字節(jié)作為一個(gè)權(quán)重,對(duì)比限制緩存記錄,我們就能將緩存的總占用限制在100kb左右。
2.3緩存監(jiān)控
緩存記錄的加載和命中情況是評(píng)價(jià)緩存處理能力的重要指標(biāo),Guava cache提供了stat統(tǒng)計(jì)日志對(duì)這兩個(gè)指標(biāo)進(jìn)行了統(tǒng)計(jì),我們只需要在創(chuàng)建緩存容器的時(shí)候加上recordStats就可以開啟統(tǒng)計(jì)。
public Cache createCache() { return CacheBuilder.newBuilder() .recordStats() .build(); }
2.4 Guava cache的優(yōu)劣勢和適用場景
優(yōu)劣勢:Guava cache通過內(nèi)存處理數(shù)據(jù),具有減少IO請(qǐng)求,讀寫性能快的優(yōu)勢,但是受內(nèi)存容量限制,只能處理少量數(shù)據(jù)的讀寫,還有可能對(duì)本機(jī)內(nèi)存造成壓力,并且在分布式部署中,會(huì)存在不同機(jī)器節(jié)點(diǎn)數(shù)據(jù)不一致的情況,即緩存漂移。
適用場景:讀多寫少,對(duì)數(shù)據(jù)一致性要求不高的場景。
3、Caffeine簡介
Caffeine同樣是Google開發(fā)的,是在Guava cache的基礎(chǔ)上改良而來的,底層設(shè)計(jì)思路、功能和使用方式與Guava非常類似,但是各方面的性能都要遠(yuǎn)遠(yuǎn)超過前者,可以看做是Guava cache的升級(jí)版,因此,之前使用過Guava cache,也能夠很快的上手Caffeine,下面是Caffeine和Guava cache的緩存創(chuàng)建對(duì)比,基本可以無門檻過渡。
public Cache createCache() { return Caffeine.newBuilder() .initialCapacity(1000) .maximumSize(100L) .expireAfterWrite(5L, TimeUnit.MINUTES) .recordStats() .build(); }
public Cache createCache() { return CacheBuilder.newBuilder() .initialCapacity(1000) .maximumSize(100L) .expireAfterWrite(5L, TimeUnit.MINUTES) .recordStats() .build(); }
那么Caffeine底層又做了哪些優(yōu)化,才能讓其性能高于Guava cache呢?主要包含以下三點(diǎn):
3.1、對(duì)比Guava cache的性能主要優(yōu)化項(xiàng)
(1)異步策略
Guava cache在讀操作中可能會(huì)觸發(fā)淘汰數(shù)據(jù)的清理操作,雖然自身也做了一些優(yōu)化來減少讀的時(shí)候的清理操作,但是一旦觸發(fā),就會(huì)降低查詢效率,對(duì)緩存性能產(chǎn)生影響。而在Caffeine支持異步操作,采用異步處理的策略,查詢請(qǐng)求在觸發(fā)淘汰數(shù)據(jù)的清理操作后,會(huì)將清理數(shù)據(jù)的任務(wù)添加到獨(dú)立的線程池中進(jìn)行異步操作,不會(huì)阻塞查詢請(qǐng)求,提高了查詢性能。
?
(2)ConcurrentHashMap優(yōu)化
Caffeine底層都是通過ConcurrentHashMap來進(jìn)行數(shù)據(jù)的存儲(chǔ),因此隨著Java8中對(duì)ConcurrentHashMap的調(diào)整,數(shù)組+鏈表的結(jié)構(gòu)升級(jí)為數(shù)組+鏈表+紅黑樹的結(jié)構(gòu)以及分段鎖升級(jí)為syschronized+CAS,降低了鎖的粒度,減少了鎖的競爭,這兩個(gè)優(yōu)化顯著提高了Caffeine在讀多寫少場景下的查詢性能。
?
(3)新型淘汰算法W-TinyLFU
傳統(tǒng)的淘汰算法,如LRU、LFU、FIFO,在實(shí)際的緩存場景中都存在一些弊端,如FIFO算法,如果緩存使用的頻率較高,那么緩存數(shù)據(jù)會(huì)一直處在進(jìn)進(jìn)出出的狀態(tài),間接影響到緩存命中率。LRU算法,在批量刷新緩存數(shù)據(jù)的場景下,可能會(huì)將其他緩存數(shù)據(jù)淘汰掉,從而帶來緩存擊穿的風(fēng)險(xiǎn)。LFU算法,需要保存緩存記錄的訪問次數(shù),帶來內(nèi)存空間的損耗。
因此,Caffeine引入了W-TinyLFU算法,由窗口緩存、過濾器、主緩存組成。緩存數(shù)據(jù)剛進(jìn)入時(shí)會(huì)停留在窗口緩存中,這個(gè)部分只占總緩存的1%,當(dāng)被擠出窗口緩存時(shí),會(huì)在過濾器匯總和主緩存中淘汰的數(shù)據(jù)進(jìn)行比較,如果頻率更高,則進(jìn)入主緩存,否則就被淘汰,主緩存被分為淘汰段和保護(hù)段,兩段都是LRU算法,第一次被訪問的元素會(huì)進(jìn)入淘汰段,第二次被訪問會(huì)進(jìn)入保護(hù)段,保護(hù)段中被淘汰的元素會(huì)進(jìn)入淘汰段,這種算法實(shí)現(xiàn)了高命中率和低內(nèi)存占用。更詳細(xì)的解釋可以參考論文:https://arxiv.org/pdf/1512.00727.pdf?
3.2、Caffeine的優(yōu)劣勢和適用場景
優(yōu)勢:對(duì)比Guava cache有更高的緩存性能,劣勢:仍然存在緩存漂移的問題;JDK版本過低無法使用
適用場景:1、適用場景:讀多寫少,對(duì)數(shù)據(jù)一致性要求不高的場景;2、純內(nèi)存緩存,JDK8及更高版本中,追求比Guava cache更高的性能。
4、Ehcache簡介
Guava cache和Caffeine都是JVM緩存,會(huì)受到內(nèi)存大小的制約,最新的Ehcache采用堆內(nèi)緩存+堆外緩存+磁盤的方式,打破了這一制約。堆內(nèi)緩存就是被JVM管理的那一部分緩存,而堆外緩存,就是在內(nèi)存中另外在開辟一塊不被JVM管理的部分。堆外緩存這部分既可以享受內(nèi)存的高速讀寫能力,而且又避免的JVM頻繁的GC,缺點(diǎn)是需要自行清理數(shù)據(jù)。
下面是Ehcache緩存的創(chuàng)建,指定了堆內(nèi)、堆外緩存和磁盤緩存的大小。
ResourcePoolsBuilder.newResourcePoolsBuilder() .heap(20, MemoryUnit.MB) .offheap(10, MemoryUnit.MB) .disk(5, MemoryUnit.GB);
為了解決緩存漂移的問題,Ehcache支持通過集群的方式,實(shí)現(xiàn)了分布式節(jié)點(diǎn)之間的數(shù)據(jù)互通。關(guān)于Ehcache的集群策略,后續(xù)文章再詳細(xì)闡述。
5、不同本地緩存對(duì)比
框架 | 命中率 | 速度 | 回收算法 | 使用難度 | 集群 | 適用場景 |
---|---|---|---|---|---|---|
Guava cache | 中 | 第三 | LRU、LFU、FIFO | 易 | 不支持 | 讀多寫少,允許少量緩存偏移 |
Caffeine | 高 | 第一 | W-TinyLFU | 易 | 不支持 | 讀多寫少,允許少量緩存偏移,能用Caffeine就別用Guava cache |
Ehcache | 中 | 第二 | LRU、LFU、FIFO | 中 | 支持 | 分布式系統(tǒng)中對(duì)數(shù)據(jù)一致性要求高 |
?
-
JAVA
+關(guān)注
關(guān)注
19文章
2974瀏覽量
105094 -
緩存
+關(guān)注
關(guān)注
1文章
241瀏覽量
26740 -
Redis
+關(guān)注
關(guān)注
0文章
378瀏覽量
10926
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論