緩存類型
- JVM 緩存
首先是 JVM 緩存拗踢,也可以認為是堆緩存亲善。其實就是創(chuàng)建一些全局變量傲宜,如 Map、List 之類的容器用于存放數(shù)據(jù)侦铜。這樣的優(yōu)勢是使用簡單但是也有以下問題:- 只能顯式的寫入专甩,清除數(shù)據(jù)。
- 不能按照一定的規(guī)則淘汰數(shù)據(jù)钉稍,如 LRU涤躲,LFU,F(xiàn)IFO 等贡未。
- 清除數(shù)據(jù)時的回調(diào)通知种樱。
- 其他一些定制功能等。
- Ehcache俊卤、Guava Cache
所以出現(xiàn)了一些專門用作 JVM 緩存的開源工具出現(xiàn)了嫩挤,如本文提到的 Guava Cache。
它具有上文 JVM 緩存不具有的功能消恍,如自動清除數(shù)據(jù)岂昭、多種清除算法、清除回調(diào)等狠怨。
但也正因為有了這些功能佩抹,這樣的緩存必然會多出許多東西需要額外維護,自然也就增加了系統(tǒng)的消耗取董。 - 分布式緩存
剛才提到的兩種緩存其實都是堆內(nèi)緩存,只能在單個節(jié)點中使用无宿,這樣在分布式場景下就招架不住了茵汰。
于是也有了一些緩存中間件,如 Redis孽鸡、Memcached蹂午,在分布式環(huán)境下可以共享內(nèi)存栏豺。具體不在本次的討論范圍。
final static Cache<Integer, String> cache = CacheBuilder.newBuilder()
//設(shè)置cache的初始大小為10豆胸,要合理設(shè)置該值
.initialCapacity(10)
//設(shè)置并發(fā)數(shù)為5奥洼,即同一時間最多只能有5個線程往cache執(zhí)行寫入操作
.concurrencyLevel(5)
//設(shè)置cache中的數(shù)據(jù)在寫入之后的存活時間為10秒
.expireAfterWrite(10, TimeUnit.SECONDS)
//構(gòu)建cache實例
.build();
清除緩存的策略
任何Cache的容量都是有限的,而緩存清除策略就是決定數(shù)據(jù)在什么時候應(yīng)該被清理掉晚胡。GuavaCache提了以下幾種清除策略:
- 基于存活時間的清除(Timed Eviction)
這應(yīng)該是最常用的清除策略灵奖,在構(gòu)建Cache實例的時候,CacheBuilder提供兩種基于存活時間的構(gòu)建方法:
-
expireAfterAccess(long, TimeUnit)
:緩存項在創(chuàng)建后估盘,在給定時間內(nèi)沒有被讀/寫訪問瓷患,則清除。 -
expireAfterWrite(long, TimeUnit)
:緩存項在創(chuàng)建后遣妥,在給定時間內(nèi)沒有被寫訪問(創(chuàng)建或覆蓋)擅编,則清除。expireAfterWrite()
方法有些類似于redis中的expire命令箫踩,但顯然它只能設(shè)置所有緩存都具有相同的存活時間爱态。若遇到一些緩存數(shù)據(jù)的存活時間為1分鐘,一些為5分鐘境钟,那只能構(gòu)建兩個Cache實例了锦担。
- 基于容量的清除(size-based eviction)
- 在構(gòu)建Cache實例的時候,通過
CacheBuilder.maximumSize(long)
方法可以設(shè)置Cache的最大容量數(shù)吱韭,當緩存數(shù)量達到或接近該最大值時吆豹,Cache將清除掉那些最近最少使用的緩存。 - 以上是這種方式是以緩存的“數(shù)量”作為容量的計算方式理盆,還有另外一種基于“權(quán)重”的計算方式痘煤。比如每一項緩存所占據(jù)的內(nèi)存空間大小都不一樣,可以看作它們有不同的“權(quán)重”(weights)猿规。你可以使用
CacheBuilder.weigher(Weigher)
指定一個權(quán)重函數(shù)衷快,并且用CacheBuilder.maximumWeight(long)
指定最大總重。
- 顯式清除
任何時候姨俩,你都可以顯式地清除緩存項蘸拔,而不是等到它被回收,Cache接口提供了如下API:
- 個別清除:
Cache.invalidate(key)
- 批量清除:
Cache.invalidateAll(keys)
- 清除所有緩存項:
Cache.invalidateAll()
基于引用的清除(Reference-based Eviction)
在構(gòu)建Cache實例過程中环葵,通過設(shè)置使用弱引用的鍵调窍、或弱引用的值、或軟引用的值张遭,從而使JVM在GC時順帶實現(xiàn)緩存的清除邓萨,不過一般不輕易使用這個特性。
-
CacheBuilder.weakKeys()
:使用弱引用存儲鍵 -
CacheBuilder.weakValues()
:使用弱引用存儲值 -
CacheBuilder.softValues()
:使用軟引用存儲值
清除什么時候發(fā)生?
也許這個問題有點奇怪缔恳,如果設(shè)置的存活時間為一分鐘宝剖,難道不是一分鐘后這個key就會立即清除掉嗎?我們來分析一下如果要實現(xiàn)這個功能歉甚,那Cache中就必須存在線程來進行周期性地檢查万细、清除等工作,很多cache如redis纸泄、ehcache都是這樣實現(xiàn)的赖钞。
但在GuavaCache中,并不存在任何線程刃滓!它實現(xiàn)機制是在寫操作時順帶做少量的維護工作(如清除)仁烹,偶爾在讀操作時做(如果寫操作實在太少的話),也就是說在使用的是調(diào)用線程咧虎。
這在GuavaCache被稱為“延遲刪除”卓缰,即刪除總是發(fā)生得比較“晚”,這也是GuavaCache不同于其他Cache的地方砰诵!這種實現(xiàn)方式的問題:緩存會可能會存活比較長的時間征唬,一直占用著內(nèi)存。如果使用了復(fù)雜的清除策略如基于容量的清除茁彭,還可能會占用著線程而導(dǎo)致響應(yīng)時間變長总寒。但優(yōu)點也是顯而易見的,沒有啟動線程理肺,不管是實現(xiàn)摄闸,還是使用起來都讓人覺得簡單(輕量)。
如果你還是希望盡可能的降低延遲妹萨,可以創(chuàng)建自己的維護線程年枕,以固定的時間間隔調(diào)用Cache.cleanUp()
,ScheduledExecutorService
可以幫助你很好地實現(xiàn)這樣的定時調(diào)度乎完。不過這種方式依然沒辦法百分百的確定一定是自己的維護線程“命中”了維護的工作熏兄。