Guava Cache的使用簡介
1 引入
說到緩存,可能大家最先想到的還是Redis。作為基于鍵值對的非關(guān)系型數(shù)據(jù)庫,Redis具有高性能、豐富的數(shù)據(jù)結(jié)構(gòu)、持久化、高可用、分布式等特性,使其在業(yè)內(nèi)得到了廣泛的認(rèn)可和使用。但是,使用Redis必然涉及到網(wǎng)絡(luò)連接,當(dāng)網(wǎng)絡(luò)連接不穩(wěn)定或網(wǎng)絡(luò)耗時嚴(yán)重時,必然會影響到我們的業(yè)務(wù)使用。如果我們想提高我們的業(yè)務(wù)性能,又減少對其他機(jī)器的依賴,那么,使用本地緩存會是一個不錯的選擇。
使用本地緩存時,大多時候我們會采用ConcurrentHashMap來實(shí)現(xiàn)。對于本地緩存的使用,現(xiàn)在有一些較為成熟的本地緩存工具,如ehcache、guava cache,以及Caffeine。當(dāng)需要對緩存進(jìn)行持久化操作時,可以考慮使用ehcache。如果沒有持久化操作,可以考慮使用guava cache或caffeine,caffeine是基于guava cache進(jìn)行的二次優(yōu)化,可根據(jù)自身業(yè)務(wù)需要選擇使用哪一種本地緩存工具,本文將針對在DRS系統(tǒng)中使用到的Guava Cache進(jìn)行講解。
在本次項(xiàng)目中,選擇的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鍵值對的方式進(jìn)行數(shù)據(jù)的存儲,都采用了分段加鎖的方式來提高多線程下的并發(fā)操作。不同的是,使用ConcurrentHashMap進(jìn)行緩存存儲時,會一直保留緩存數(shù)據(jù),直到系統(tǒng)重啟或顯示刪除緩存,而guava cache支持緩存過期時間的設(shè)置,可以進(jìn)行緩存清理操作。同時,guava cache還可以自動加載緩存,當(dāng)我們需要從數(shù)據(jù)庫中加載數(shù)據(jù)到緩存時,不需要每次在獲取緩存時判斷緩存是否存在,guava cache會按照我們設(shè)置的加載方式進(jìn)行數(shù)據(jù)的加載。除此之外,guava cache還可以進(jìn)行一些統(tǒng)計(jì)操作,如統(tǒng)計(jì)緩存的命中率、加載新值的平均時間等。正是由于Guava cache的這些特性,我們才選擇它應(yīng)用于DRS系統(tǒng)中。下面,來看看我們是如何使用Guava Cache進(jìn)行緩存管理操作的吧。
3 緩存的過期時間設(shè)置
Guava Cache支持三種過期設(shè)置,分別是基于容量的回收、定時回收,以及基于引用的回收。顯然,基于容量的回收和基于引用的回收跟緩存時間沒關(guān)系。當(dāng)我們需要設(shè)置緩存的緩存的過期時間時,使用的必然是定時回收的回收策略,那么,在guava cache中,支持兩種定時回收策略,分別是基于讀寫訪問的回收與基于寫訪問的回收,也就是緩存在設(shè)置的時間內(nèi)沒有被讀寫訪問或?qū)懺L問,緩存將會被回收,這種特性是可以滿足我們過期時間設(shè)置要求的。這兩種方法分別是: expireAfterAccess(long, TimeUnit):基于讀寫訪問的回收,緩存項(xiàng)在給定時間內(nèi)沒有被讀/寫訪問,則回收 expireAfterWrite(long, TimeUnit):基于寫訪問的回收,即緩存項(xiàng)在給定時間內(nèi)沒有被寫訪問,則回收 如下,給出了基于寫訪問的回收策略,過期時間設(shè)置的是3秒:
Cache<String, String> cache = CacheBuilder.newBuilder() .expireAfterWrite(3, TimeUnit.SECONDS) .build();
但是,在我們的項(xiàng)目中,需要針對不同的key設(shè)置不同的過期時間。而上面的示例中,只能滿足一種過期時間的設(shè)置,所有key的過期時間都是一樣的。為了滿足不同過期時間的需求,這里我們采用了一個兩級結(jié)構(gòu)的guava cache來實(shí)現(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的加載機(jī)制了。
4 緩存加載機(jī)制
guava cache有兩種方式加載,一種是在構(gòu)建緩存對象的時候,在build方法中設(shè)置緩存的加載方式(僅LocalCache對象可用);另一種是在獲取緩存對象的時候,通過實(shí)現(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)的緩存值不存在,則會從實(shí)現(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è)置了過期時間,那么,緩存過期時間到了后,又是怎么來清理的呢?實(shí)際上,過期后的緩存并不會“自動”進(jìn)行清理,而是在下一次進(jìn)行讀訪問或是寫訪問的時候,通過判斷當(dāng)前時間與緩存加載進(jìn)來時的時間進(jìn)行比較,如果時間差大于給定的過期時間,則會清理緩存。如果設(shè)置了緩存的加載方式,則會重新加載緩存值,緩存的加載時間也會更新。通過代碼調(diào)試可以發(fā)現(xiàn),緩存清理是在get方法里實(shí)現(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)計(jì)工作,如統(tǒng)計(jì)緩存命中率、加載新值的平均時間、緩存項(xiàng)被回收的總數(shù)等,還有緩存中斷操作和刷新操作,由于本次項(xiàng)目中沒有使用到這些特性,沒有進(jìn)行深入的了解,感興趣的同學(xué)如有使用到,可以去官方網(wǎng)站進(jìn)行深入了解,詳見ifeve.com/google-guav…
以上就是Guava Cache的使用簡介的詳細(xì)內(nèi)容,更多關(guān)于Guava Cache的使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用openoffice+jodconverter-code-3.0-bate4實(shí)現(xiàn)ppt轉(zhuǎn)圖片
這篇文章主要為大家詳細(xì)介紹了利用openoffice+jodconverter-code-3.0-bate4實(shí)現(xiàn)ppt轉(zhuǎn)圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07Java中實(shí)體與Map之間的相互轉(zhuǎn)換代碼示例
生活中經(jīng)常用到map數(shù)據(jù)與實(shí)體類的轉(zhuǎn)換,下面這篇文章主要給大家介紹了關(guān)于Java中實(shí)體與Map之間相互轉(zhuǎn)換的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12RabbitMQ消息隊(duì)列的目錄結(jié)構(gòu)
這篇文章主要介紹了RabbitMQ消息隊(duì)列的目錄結(jié)構(gòu),RabbitMQ?屬于消息中間件,主要用于組件之間的解耦,消息的發(fā)送者無需知道消息使用者的存在,反之亦然,那么用了那么久RabbitMQ,其目錄結(jié)構(gòu)是怎樣的呢,讓我們一起來看一下吧2023-08-08Mybatis控制臺打印Sql語句的實(shí)現(xiàn)代碼
MyBatis是一個支持普通SQL查詢,存儲過程和高級映射的優(yōu)秀持久層框架,下面給大家介紹Mybatis控制臺打印Sql語句的實(shí)現(xiàn)代碼,非常不錯,感興趣的朋友一起看下吧2016-07-07springboot+springmvc+mybatis項(xiàng)目整合
這篇文章主要為大家詳細(xì)介紹了springboot+springmvc+mybatis項(xiàng)目的整合,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04