Java基于LoadingCache實(shí)現(xiàn)本地緩存的示例代碼
一、 添加 maven 依賴
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.1-jre</version> </dependency>
二、CacheBuilder 方法說明
1??LoadingCache build(CacheLoader loader)
2??CacheBuilder.maximumSize(long size)配置緩存數(shù)量上限,快達(dá)到上限或達(dá)到上限,處理了時(shí)間最長沒被訪問過的對象或者根據(jù)配置的被釋放的對象
3??expireAfterAccess(long, TimeUnit)緩存項(xiàng)在給定時(shí)間內(nèi)沒有被讀/寫訪問,則回收。請注意這種緩存的回收順序和基于大小回收一樣
4??expireAfterWrite(long, TimeUnit)緩存項(xiàng)在給定時(shí)間內(nèi)沒有被寫訪問(創(chuàng)建或覆蓋),則回收。如果認(rèn)為緩存數(shù)據(jù)總是在固定時(shí)候后變得陳舊不可用,這種回收方式是可取的。
5??refreshAfterWrite(long duration, TimeUnit unit)定時(shí)刷新,可以為緩存增加自動(dòng)定時(shí)刷新功能。和expireAfterWrite相反,refreshAfterWrite通過定時(shí)刷新可以讓緩存項(xiàng)保持可用,但請注意:緩存項(xiàng)只有在被檢索時(shí)才會(huì)真正刷新,即只有刷新間隔時(shí)間到了再去get(key)才會(huì)重新去執(zhí)行Loading,否則就算刷新間隔時(shí)間到了也不會(huì)執(zhí)行l(wèi)oading操作。因此,如果在緩存上同時(shí)聲明expireAfterWrite和refreshAfterWrite,緩存并不會(huì)因?yàn)樗⑿旅つ康囟〞r(shí)重置,如果緩存項(xiàng)沒有被檢索,那刷新就不會(huì)真的發(fā)生,緩存項(xiàng)在過期時(shí)間后也變得可以回收。還有一點(diǎn)比較重要的是refreshAfterWrite和expireAfterWrite兩個(gè)方法設(shè)置以后,重新get會(huì)引起loading操作都是同步串行的。這其實(shí)可能會(huì)有一個(gè)隱患,當(dāng)某一個(gè)時(shí)間點(diǎn)剛好有大量檢索過來而且都有刷新或者回收的話,是會(huì)產(chǎn)生大量的請求同步調(diào)用loading方法,這些請求占用線程資源的時(shí)間明顯變長。如正常請求也就20ms,當(dāng)刷新以后加上同步請求loading這個(gè)功能接口可能響應(yīng)時(shí)間遠(yuǎn)遠(yuǎn)大于20ms。為了預(yù)防這種井噴現(xiàn)象,可以不設(shè)refreshAfterWrite方法,改用LoadingCache.refresh(K)因?yàn)樗钱惒綀?zhí)行的,不會(huì)影響正在讀的請求,同時(shí)使用ScheduledExecutorService可以很好地實(shí)現(xiàn)這樣的定時(shí)調(diào)度,配上cache.asMap().keySet()返回當(dāng)前所有已加載鍵,這樣所有的key定時(shí)刷新就有了。如果訪問量沒有這么大則直接用CacheBuilder.refreshAfterWrite(long, TimeUnit)也可以。
三、創(chuàng)建 CacheLoader
LoadingCache<Long, String> cache = CacheBuilder.newBuilder() ? ? ? ? //緩存池大小,在緩存項(xiàng)接近該大小時(shí), Guava開始回收舊的緩存項(xiàng) ? ? ? ? .maximumSize(10000) ? ? ? ? //設(shè)置時(shí)間對象沒有被讀/寫訪問則對象從內(nèi)存中刪除(在另外的線程里面不定期維護(hù)) ? ? ? ? .expireAfterAccess(10, TimeUnit.MINUTES) ? ? ? ? //移除監(jiān)聽器,緩存項(xiàng)被移除時(shí)會(huì)觸發(fā) ? ? ? ? .removalListener(new RemovalListener <Long, String>() { ? ? ? ? ? @Override ? ? ? ? ? public void onRemoval(RemovalNotification<Long, String> rn) { ? ? ? ? ? ? //執(zhí)行邏輯操作 ? ? ? ? ? } ? ? ? ? }) ? ? ? ? .recordStats()//開啟Guava Cache的統(tǒng)計(jì)功能 ? ? ? ? .build(new CacheLoader<String, Object>() { ? ? ? ? ? ? ? ? ? ? @Override ? ? ? ? ? ? ? ? ? ? public Object load(String key) { ? ? ? ? ? ? ? ? ? ? ? ?//從 SQL或者NoSql 獲取對象 ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? });//CacheLoader類 實(shí)現(xiàn)自動(dòng)加載
四、工具類
import com.google.common.cache.*; import org.slf4j.Logger; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; public class CacheManager { ? private static Logger log = Log.get(); ? /** 緩存項(xiàng)最大數(shù)量 */ ? private static final long GUAVA_CACHE_SIZE = 100000; ? /** 緩存時(shí)間:天 */ ? private static final long GUAVA_CACHE_DAY = 10; ? /** 緩存操作對象 */ ? private static LoadingCache<Long, String> GLOBAL_CACHE = null; ? static { ? ? try { ? ? ? GLOBAL_CACHE = loadCache(new CacheLoader <Long, String>() { ? ? ? ? @Override ? ? ? ? public String load(Long key) throws Exception { ? ? ? ? ? // 處理緩存鍵不存在緩存值時(shí)的處理邏輯 ? ? ? ? ? return ""; ? ? ? ? } ? ? ? }); ? ? } catch (Exception e) { ? ? ? log.error("初始化Guava Cache出錯(cuò)", e); ? ? } ? } ? /** ? ?* 全局緩存設(shè)置 ? ?* 緩存項(xiàng)最大數(shù)量:100000 ? ?* 緩存有效時(shí)間(天):10 ? ?* @param cacheLoader ? ?* @return ? ?* @throws Exception ? ?*/ ? private static LoadingCache<Long, String> loadCache(CacheLoader<Long, String> cacheLoader)? throws Exception { ? ? LoadingCache<Long, String> cache = CacheBuilder.newBuilder() ? ? ? ? //緩存池大小,在緩存項(xiàng)接近該大小時(shí), Guava開始回收舊的緩存項(xiàng) ? ? ? ? .maximumSize(GUAVA_CACHE_SIZE) ? ? ? ? //設(shè)置時(shí)間對象沒有被讀/寫訪問則對象從內(nèi)存中刪除(在另外的線程里面不定期維護(hù)) ? ? ? ? .expireAfterAccess(GUAVA_CACHE_DAY, TimeUnit.DAYS) ? ? ? ? // 設(shè)置緩存在寫入之后 設(shè)定時(shí)間 后失效 ? ? ? ? .expireAfterWrite(GUAVA_CACHE_DAY, TimeUnit.DAYS) ? ? ? ? //移除監(jiān)聽器,緩存項(xiàng)被移除時(shí)會(huì)觸發(fā) ? ? ? ? .removalListener(new RemovalListener <Long, String>() { ? ? ? ? ? @Override ? ? ? ? ? public void onRemoval(RemovalNotification<Long, String> rn) { ? ? ? ? ? ? //邏輯操作 ? ? ? ? ? } ? ? ? ? }) ? ? ? ? //開啟Guava Cache的統(tǒng)計(jì)功能 ? ? ? ? .recordStats() ? ? ? ? .build(cacheLoader); ? ? return cache; ? } ? /** ? ?* 設(shè)置緩存值 ? ?* 注: 若已有該key值,則會(huì)先移除(會(huì)觸發(fā)removalListener移除監(jiān)聽器),再添加 ? ?* ? ?* @param key ? ?* @param value ? ?*/ ? public static void put(Long key, String value) { ? ? try { ? ? ? GLOBAL_CACHE.put(key, value); ? ? } catch (Exception e) { ? ? ? log.error("設(shè)置緩存值出錯(cuò)", e); ? ? } ? } ? /** ? ?* 批量設(shè)置緩存值 ? ?* ? ?* @param map ? ?*/ ? public static void putAll(Map<? extends Long, ? extends String> map) { ? ? try { ? ? ? GLOBAL_CACHE.putAll(map); ? ? } catch (Exception e) { ? ? ? log.error("批量設(shè)置緩存值出錯(cuò)", e); ? ? } ? } ? /** ? ?* 獲取緩存值 ? ?* 注:如果鍵不存在值,將調(diào)用CacheLoader的load方法加載新值到該鍵中 ? ?* ? ?* @param key ? ?* @return ? ?*/ ? public static String get(Long key) { ? ? String token = ""; ? ? try { ? ? ? token = GLOBAL_CACHE.get(key); ? ? } catch (Exception e) { ? ? ? log.error("獲取緩存值出錯(cuò)", e); ? ? } ? ? return token; ? } ?/** ? ?* 移除緩存 ? ?* @param key ? ?*/ ? public static void remove(Long key) { ? ? try { ? ? ? GLOBAL_CACHE.invalidate(key); ? ? } catch (Exception e) { ? ? ? log.error("移除緩存出錯(cuò)", e); ? ? } ? } ? /** ? ?* 批量移除緩存 ? ?* @param keys ? ?*/ ? public static void removeAll(Iterable<Long> keys) { ? ? try { ? ? ? GLOBAL_CACHE.invalidateAll(keys); ? ? } catch (Exception e) { ? ? ? log.error("批量移除緩存出錯(cuò)", e); ? ? } ? } ? /** ? ?* 清空所有緩存 ? ?*/ ? public static void removeAll() { ? ? try { ? ? ? GLOBAL_CACHE.invalidateAll(); ? ? } catch (Exception e) { ? ? ? log.error("清空所有緩存出錯(cuò)", e); ? ? } ? } ? /** ? ?* 獲取緩存項(xiàng)數(shù)量 ? ?* @return ? ?*/ ? public static long size() { ? ? long size = 0; ? ? try { ? ? ? size = GLOBAL_CACHE.size(); ? ? } catch (Exception e) { ? ? ? log.error("獲取緩存項(xiàng)數(shù)量出錯(cuò)", e); ? ? } ? ? return size; ? } }
五、guava Cache數(shù)據(jù)移除
1??移除機(jī)制
guava做cache的時(shí)候,數(shù)據(jù)的移除分為被動(dòng)移除和主動(dòng)移除兩種。
【被動(dòng)移除分為三種】
1)基于大小的移除:
按照緩存的大小來移除,如果即將到達(dá)指定的大小,那就會(huì)把不常用的鍵值對從cache中移除。定義的方式一般為 CacheBuilder.maximumSize(long),還有一種可以算權(quán)重的方法,個(gè)人認(rèn)為實(shí)際使用中不太用到。就這個(gè)常用有一下注意點(diǎn):
a. 這個(gè)size指的是cache中的條目數(shù),不是內(nèi)存大小或是其他;
b. 并不是完全到了指定的size系統(tǒng)才開始移除不常用的數(shù)據(jù)的,而是接近這個(gè)size的時(shí)候系統(tǒng)就會(huì)開始做移除的動(dòng)作;
c. 如果一個(gè)鍵值對已經(jīng)從緩存中被移除了,再次請求訪問的時(shí)候,如果cachebuild是使用cacheloader方式的,那依然還是會(huì)從cacheloader中再取一次值,如果這樣還沒有,就會(huì)拋出異常。
2)基于時(shí)間的移除:
expireAfterAccess(long, TimeUnit) 根據(jù)某個(gè)鍵值對最后一次訪問之后多少時(shí)間后移除;
expireAfterWrite(long, TimeUnit) 根據(jù)某個(gè)鍵值對被創(chuàng)建或值被替換后多少時(shí)間移除
3)基于引用的移除:主要是基于Java的垃圾回收機(jī)制,根據(jù)鍵或者值的引用關(guān)系決定移除
【主動(dòng)移除分為三種】
1)單獨(dú)移除:Cache.invalidate(key)
2)批量移除:Cache.invalidateAll(keys)
3)移除所有:Cache.invalidateAll()
如果需要在移除數(shù)據(jù)的時(shí)候有所動(dòng)作還可以定義Removal Listener,但是有點(diǎn)需要注意的是默認(rèn)Removal Listener中的行為是和移除動(dòng)作同步執(zhí)行的,如果需要改成異步形式,可以考慮使用RemovalListeners.asynchronous(RemovalListener, Executor)
2??遇到的問題
1)在put操作之前,如果已經(jīng)有該鍵值,會(huì)先觸發(fā)removalListener移除監(jiān)聽器,再添加
2)配置了expireAfterAccess和expireAfterWrite,但在指定時(shí)間后沒有被移除。
解決方案:CacheBuilder在文檔上有說明:
If expireAfterWrite or expireAfterAccess is requested entries may be evicted on each cache modification, on occasional cache accesses, or on calls to Cache.cleanUp(). Expired entries may be counted in Cache.size(), but will never be visible to read or write operations.
翻譯過來大概的意思是:CacheBuilder構(gòu)建的緩存不會(huì)在特定時(shí)間自動(dòng)執(zhí)行清理和回收工作,也不會(huì)在某個(gè)緩存項(xiàng)過期后馬上清理,它不會(huì)啟動(dòng)一個(gè)線程來進(jìn)行緩存維護(hù),因?yàn)椋?/p>
a)線程相對較重
b)某些環(huán)境限制線程的創(chuàng)建。它會(huì)在寫操作時(shí)順帶做少量的維護(hù)工作,或者偶爾在讀操作時(shí)做
當(dāng)然,也可以創(chuàng)建自己的維護(hù)線程,以固定的時(shí)間間隔調(diào)用Cache.cleanUp()。
到此這篇關(guān)于Java基于LoadingCache實(shí)現(xiàn)本地緩存的示例代碼的文章就介紹到這了,更多相關(guān)Java LoadingCache本地緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 序列化詳解及簡單實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了 Java 序列化詳解及簡單實(shí)現(xiàn)實(shí)例的相關(guān)資料,使用序列化目的:以某種存儲(chǔ)形式使自定義對象持久化,將對象從一個(gè)地方傳遞到另一個(gè)地方,需要的朋友可以參考下2017-03-03SpringMvc實(shí)現(xiàn)簡易計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了SpringMvc實(shí)現(xiàn)簡易計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07Spring觀察者模式之事件發(fā)布訂閱實(shí)現(xiàn)和源碼詳解
這篇文章主要介紹了Spring觀察者模式之事件發(fā)布訂閱實(shí)現(xiàn)和源碼詳解,Spring認(rèn)為發(fā)布訂閱主題,其實(shí)可以理解為事件驅(qū)動(dòng)的編碼,先來實(shí)現(xiàn)以下Spring容器中的事件發(fā)布訂閱,需要的朋友可以參考下2024-01-01解決springboot利用ConfigurationProperties注解配置數(shù)據(jù)源無法讀取配置信息問題
今天在學(xué)習(xí)springboot利用ConfigurationProperties注解配置數(shù)據(jù)源的使用遇到一個(gè)問題無法讀取配置信息,發(fā)現(xiàn)全部為null,糾結(jié)是哪里出了問題呢,今天一番思考,問題根源找到,下面把我的解決方案分享到腳本之家平臺(tái),感興趣的朋友一起看看吧2021-05-05java獲取文件擴(kuò)展名的方法小結(jié)【正則與字符串截取】
這篇文章主要介紹了java獲取文件擴(kuò)展名的方法,結(jié)合實(shí)例形式分析了使用正則與字符串截取兩種獲取擴(kuò)展名的操作技巧,需要的朋友可以參考下2017-01-01java中httpclient封裝post請求和get的請求實(shí)例
這篇文章主要介紹了java中httpclient封裝post請求和get的請求實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10