Guava Cache的使用簡介
1 引入
說到緩存,可能大家最先想到的還是Redis。作為基于鍵值對的非關(guān)系型數(shù)據(jù)庫,Redis具有高性能、豐富的數(shù)據(jù)結(jié)構(gòu)、持久化、高可用、分布式等特性,使其在業(yè)內(nèi)得到了廣泛的認可和使用。但是,使用Redis必然涉及到網(wǎng)絡(luò)連接,當(dāng)網(wǎng)絡(luò)連接不穩(wěn)定或網(wǎng)絡(luò)耗時嚴(yán)重時,必然會影響到我們的業(yè)務(wù)使用。如果我們想提高我們的業(yè)務(wù)性能,又減少對其他機器的依賴,那么,使用本地緩存會是一個不錯的選擇。
使用本地緩存時,大多時候我們會采用ConcurrentHashMap來實現(xiàn)。對于本地緩存的使用,現(xiàn)在有一些較為成熟的本地緩存工具,如ehcache、guava cache,以及Caffeine。當(dāng)需要對緩存進行持久化操作時,可以考慮使用ehcache。如果沒有持久化操作,可以考慮使用guava cache或caffeine,caffeine是基于guava cache進行的二次優(yōu)化,可根據(jù)自身業(yè)務(wù)需要選擇使用哪一種本地緩存工具,本文將針對在DRS系統(tǒng)中使用到的Guava Cache進行講解。
在本次項目中,選擇的guava cache版本信息如下:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1-jre</version> </dependency>
2 Guava Cache介紹
Guava cache和ConcurrentHashMap很相似,都是采用的key-value鍵值對的方式進行數(shù)據(jù)的存儲,都采用了分段加鎖的方式來提高多線程下的并發(fā)操作。不同的是,使用ConcurrentHashMap進行緩存存儲時,會一直保留緩存數(shù)據(jù),直到系統(tǒng)重啟或顯示刪除緩存,而guava cache支持緩存過期時間的設(shè)置,可以進行緩存清理操作。同時,guava cache還可以自動加載緩存,當(dāng)我們需要從數(shù)據(jù)庫中加載數(shù)據(jù)到緩存時,不需要每次在獲取緩存時判斷緩存是否存在,guava cache會按照我們設(shè)置的加載方式進行數(shù)據(jù)的加載。除此之外,guava cache還可以進行一些統(tǒng)計操作,如統(tǒng)計緩存的命中率、加載新值的平均時間等。正是由于Guava cache的這些特性,我們才選擇它應(yīng)用于DRS系統(tǒng)中。下面,來看看我們是如何使用Guava Cache進行緩存管理操作的吧。
3 緩存的過期時間設(shè)置
Guava Cache支持三種過期設(shè)置,分別是基于容量的回收、定時回收,以及基于引用的回收。顯然,基于容量的回收和基于引用的回收跟緩存時間沒關(guān)系。當(dāng)我們需要設(shè)置緩存的緩存的過期時間時,使用的必然是定時回收的回收策略,那么,在guava cache中,支持兩種定時回收策略,分別是基于讀寫訪問的回收與基于寫訪問的回收,也就是緩存在設(shè)置的時間內(nèi)沒有被讀寫訪問或?qū)懺L問,緩存將會被回收,這種特性是可以滿足我們過期時間設(shè)置要求的。這兩種方法分別是: expireAfterAccess(long, TimeUnit):基于讀寫訪問的回收,緩存項在給定時間內(nèi)沒有被讀/寫訪問,則回收 expireAfterWrite(long, TimeUnit):基于寫訪問的回收,即緩存項在給定時間內(nèi)沒有被寫訪問,則回收 如下,給出了基于寫訪問的回收策略,過期時間設(shè)置的是3秒:
Cache<String, String> cache = CacheBuilder.newBuilder() .expireAfterWrite(3, TimeUnit.SECONDS) .build();
但是,在我們的項目中,需要針對不同的key設(shè)置不同的過期時間。而上面的示例中,只能滿足一種過期時間的設(shè)置,所有key的過期時間都是一樣的。為了滿足不同過期時間的需求,這里我們采用了一個兩級結(jié)構(gòu)的guava cache來實現(xiàn),如下所示:
private Cache<String, Cache<String, String>> cacheCache = CacheBuilder.newBuilder().build();
public void init() {
Cache<String, String> cache1 = CacheBuilder.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)
.build();
cache1.put("appName", "drs-server");
Cache<String, String> cache2 = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build();
cache2.put("appName", "drs-ops");
cacheCache.put("key1", cache1);
cacheCache.put("key2", cache2);
}
在上面的示例中,還可以使用Map+guava cache的方式,也就是一個map里面套一個guava cache,定義如下:
Map<String, Cache<String, String>> cacheMap = new HashMap<String, Cache<String, String>>();
那為什么我們要使用一個兩級的cache結(jié)構(gòu)而不是Map+cache的方式呢?原因在于,當(dāng)讀取的數(shù)據(jù)在緩存中沒有時,我們是希望能夠自動去從數(shù)據(jù)庫加載的,所以使用兩級的cache結(jié)構(gòu)更合適。說到這里,就不得不提到guava cache的加載機制了。
4 緩存加載機制
guava cache有兩種方式加載,一種是在構(gòu)建緩存對象的時候,在build方法中設(shè)置緩存的加載方式(僅LocalCache對象可用);另一種是在獲取緩存對象的時候,通過實現(xiàn)Callable接口方法的方式設(shè)置加載方式。 采用如下的方式構(gòu)建緩存對象:
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, String>() {
@Override
public String load(String s) throws Exception {
return "load: " + UUID.randomUUID().toString().substring(1, 6);
}
});
通過上述方式構(gòu)建完LocalCache的緩存對象,當(dāng)調(diào)用get(K key)方法獲取緩存時,如果指定的key對應(yīng)的緩存值不存在,則會從load的方法中加載對象。但是,如果使用的是getIfPresent方法,則不會從執(zhí)行l(wèi)oad方法中加載緩存值,而是返回null值。當(dāng)時用get(K key, Callable call) 獲取緩存時,如果對應(yīng)的緩存值不存在,則會從實現(xiàn)的Callable接口方法中來加載緩存。完整的示例代碼如下所示:
public static void main(String[] args) throws Exception{
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, String>() {
@Override
public String load(String s) throws Exception {
return "load: " + UUID.randomUUID().toString().substring(1, 6);
}
});
System.out.println(cache.getIfPresent("appName1"));
System.out.println(cache.get("appName2"));
String loadValue = cache.get("appName3", new Callable<String>() {
@Override
public String call() throws Exception {
return "call: " + UUID.randomUUID().toString().substring(1, 6);
}
});
System.out.println(loadValue);
}
測試結(jié)果如下:

5 緩存清理
既然我們給緩存設(shè)置了過期時間,那么,緩存過期時間到了后,又是怎么來清理的呢?實際上,過期后的緩存并不會“自動”進行清理,而是在下一次進行讀訪問或是寫訪問的時候,通過判斷當(dāng)前時間與緩存加載進來時的時間進行比較,如果時間差大于給定的過期時間,則會清理緩存。如果設(shè)置了緩存的加載方式,則會重新加載緩存值,緩存的加載時間也會更新。通過代碼調(diào)試可以發(fā)現(xiàn),緩存清理是在get方法里實現(xiàn)的,用到的以下幾個方法:
get(Object key, int hash); // 獲取緩存值 getLiveEntry(Object key, int hash, long now); // 獲取存活的對象 isExpired(ReferenceEntry<K, V> entry, long now); // 判斷是否過期 expireEntries(long now); // 清理緩存
其中,判斷是否過期的邏輯如下:

如果緩存已過期,則返回true,然后執(zhí)行緩存清理刪除操作,如下所示:

除此之外,guava cache還支持緩存的一些統(tǒng)計工作,如統(tǒng)計緩存命中率、加載新值的平均時間、緩存項被回收的總數(shù)等,還有緩存中斷操作和刷新操作,由于本次項目中沒有使用到這些特性,沒有進行深入的了解,感興趣的同學(xué)如有使用到,可以去官方網(wǎng)站進行深入了解,詳見ifeve.com/google-guav…
以上就是Guava Cache的使用簡介的詳細內(nèi)容,更多關(guān)于Guava Cache的使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用openoffice+jodconverter-code-3.0-bate4實現(xiàn)ppt轉(zhuǎn)圖片
這篇文章主要為大家詳細介紹了利用openoffice+jodconverter-code-3.0-bate4實現(xiàn)ppt轉(zhuǎn)圖片,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07
springboot+springmvc+mybatis項目整合
這篇文章主要為大家詳細介紹了springboot+springmvc+mybatis項目的整合,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-04-04

