Java設(shè)置Map過期時間的的幾種方法舉例詳解
一、技術(shù)背景
在實際的項目開發(fā)中,我們經(jīng)常會使用到緩存中間件(如redis、MemCache等)來幫助我們提高系統(tǒng)的可用性和健壯性。
但是很多時候如果項目比較簡單,就沒有必要為了使用緩存而專門引入Redis等等中間件來加重系統(tǒng)的復(fù)雜性。那么使用Java本身自己的輕量級的緩存組件就是完美解決方式。
二、技術(shù)效果
- 實現(xiàn)緩存的常見功能
- 熱點數(shù)據(jù)預(yù)熱
- 簡單限流
- 去重
三、ExpiringMap
3.1 ExpiringMap簡介
ExpiringMap具有高性能、低開銷、零依賴、線程安全、使用 ConcurrentMap 的實現(xiàn)過期 entries 等優(yōu)點。
功能包括不限于:
- 設(shè)置 Map 中的 Entry 在一段時間后自動過期。
- 設(shè)置 Map 最大容納值,當(dāng)?shù)竭_ Max size 后,再次插入值會導(dǎo)致 Map 中的第一個值過期。
- 設(shè)置 添加監(jiān)聽事件,在監(jiān)聽到 Entry 過期時調(diào)度監(jiān)聽函數(shù)。
- 設(shè)置懶加載,在調(diào)用 get() 方法時創(chuàng)建對象。
- 允許您了解條目預(yù)計何時過期
3.2 ExpiringMap使用
3.2.1 pom.xml 中添加依賴
<!-- https://mvnrepository.com/artifact/net.jodah/expiringmap --> <dependency> <groupId>net.jodah</groupId> <artifactId>expiringmap</artifactId> <version>0.5.10</version> </dependency>
3.2.2 代碼中使用
/** * ① maxSize:Map存儲的最大值,類似隊列,容量固定,當(dāng)操作map容量超出限制時,最開始的元素就會依次過期,只保留最新的; * ② expiration:過期時間; * ③ expirationListener:過期監(jiān)聽,當(dāng)條目過期時,將同步調(diào)用過期偵聽器,并且在偵聽器完成之前, * 將阻止對映射的寫入操作。還可以在單獨的線程池中配置和調(diào)用異步過期偵聽器,而不會阻塞映射操作; * ④ expirationPolicy:過期策略,包括 ExpirationPolicy.ACCESSED 和 ExpirationPolicy.CREATED 兩種; * 1)ExpirationPolicy.ACCESSED :每進行一次訪問,過期時間就會自動清零,重新計算; * 2)ExpirationPolicy.CREATED:在過期時間內(nèi)重新 put 值的話,過期時間會清理,重新計算; * ⑤ variableExpiration:可變過期,條目可以具有單獨可變的到期時間和策略: */ public static ExpiringMap<String, String> map = ExpiringMap.builder() .maxSize(1000) .expiration(2, TimeUnit.HOURS) .variableExpiration() .expirationPolicy(ExpirationPolicy.ACCESSED) .expirationListener((key, value) -> { System.out.println("SseEmitter已過期,key:"+ key); }) .build();
3.2.3 參數(shù)說明
① maxSize:Map存儲的最大值,類似隊列,容量固定,當(dāng)操作map容量超出限制時,最開始的元素就會依次過期,只保留最新的; ② expiration:過期時間; ③ expirationListener:過期監(jiān)聽,當(dāng)條目過期時,將同步調(diào)用過期偵聽器,并且在偵聽器完成之前, 將阻止對映射的寫入操作。還可以在單獨的線程池中配置和調(diào)用異步過期偵聽器,而不會阻塞映射操作; ④ expirationPolicy:過期策略,包括 ExpirationPolicy.ACCESSED 和 ExpirationPolicy.CREATED 兩種; 1)ExpirationPolicy.ACCESSED :每進行一次訪問,過期時間就會自動清零,重新計算; 2)ExpirationPolicy.CREATED:在過期時間內(nèi)重新 put 值的話,過期時間會清理,重新計算; ⑤ variableExpiration:可變過期,條目可以具有單獨可變的到期時間和策略;
3.2.4 其他使用方式
//為單個條目指定到期策略: map.put("1", "張三", ExpirationPolicy.CREATED); map.put("2", "李四", ExpirationPolicy.ACCESSED); //variableExpiration 可變過期 條目可以具有單獨可變的到期時間和策略: map.put("3", "王五", ExpirationPolicy.ACCESSED, 5, TimeUnit.MINUTES); //過期時間和策略也可以即時更改: map.setExpiration("1", 5, TimeUnit.MINUTES); map.setExpirationPolicy("1", ExpirationPolicy.ACCESSED); //動態(tài)添加和刪除過期偵聽器: ExpirationListener<String, String> connectionCloser = (key, value) -> System.out.println(key+":"+value); //添加偵聽器 map.addExpirationListener(connectionCloser); //移除偵聽器 map.removeExpirationListener(connectionCloser); //設(shè)置懶加載 // Map<String, String> stringMap = ExpiringMap.builder() // .expiration(10, TimeUnit.MINUTES) // .entryLoader(address -> address) // .build(); // // 通過 EntryLoader 將值加載到map中 // String value = stringMap.get("1"); // System.out.println("value值:"+value); //獲取條目的到期時間:單位:毫秒 long expiration = map.getExpectedExpiration("1"); System.out.println("距離過期時間還有:"+expiration+"毫秒"); //重置條目的內(nèi)部到期計時器: map.resetExpiration("1"); //查看設(shè)置的過期時間 map.getExpiration("1"); System.out.println("設(shè)置的過期時間:"+map.getExpiration("1"));
測試結(jié)果
距離過期時間還有:299999毫秒
設(shè)置的過期時間:300000
四、Guava的LoadingCache
4.1 LoadingCache簡介
做java的我們都知道Guava是一個編程工具類庫,其中包含了很多高質(zhì)量高性能的工具類和方法。其中,LoadingCache便是一個特別好用的功能,其背后的架構(gòu)其實就是Guava cache,Guava Cache 是一個全內(nèi)存的本地緩存實現(xiàn),它提供了線程安全的實現(xiàn)機制,它可以加載緩存中不存在的數(shù)據(jù),本質(zhì)其實是一個鍵值對(key-value)的緩存,可以通過key獲取到對應(yīng)的緩存值value。
特點:提供緩存回收機制,監(jiān)控緩存加載/命中情況,靈活強大的功能,簡單易上手的api。
4.2 LoadingCache使用
4.2.1 pom.xml 中添加依賴
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>24.1-jre</version> </dependency>
4.2.2 代碼中使用
public static LoadingCache<Long, User> userCache= CacheBuilder.newBuilder() // 緩存池大小,在緩存數(shù)量到達該大小時, 開始回收舊的數(shù)據(jù) .maximumSize(1000) // 設(shè)置時間60s對象沒有被讀/寫訪問則對象從內(nèi)存中刪除 .expireAfterAccess(60, TimeUnit.SECONDS) // 設(shè)置緩存在寫入之后 設(shè)定時間60s后失效 .expireAfterWrite(60, TimeUnit.SECONDS) // 定時刷新,設(shè)置時間10s后,當(dāng)有訪問時會重新執(zhí)行l(wèi)oad方法重新加載 .refreshAfterWrite(10, TimeUnit.SECONDS) // 移除監(jiān)聽器,緩存項被移除時會觸發(fā) .removalListener(new RemovalListener() { @Override public void onRemoval(RemovalNotification rn) { // 處理緩存鍵不存在緩存值時的處理邏輯 log.error(rn.getKey() + "remove"); } }) // 處理緩存鍵對應(yīng)的緩存值不存在時的處理邏輯 .build(new CacheLoader<Long, User>() { @Override public User load(Long id) { return getById(id); } }); public User getUser(Long id) { User user = userCache.get(id); } public ImmutableMap<Long, User > getAll(List<Long> ids) throws ExecutionException { return cache.getAll(ids); }
4.2.3 參數(shù)說明
① maximumSize:緩存的k-v最大數(shù)據(jù),當(dāng)總緩存的數(shù)據(jù)量達到這個值時,就會淘汰它認為不太用的一份數(shù)據(jù),會使用LRU策略進行回收; ② expireAfterAccess:緩存項在給定時間內(nèi)沒有被讀/寫訪問,則回收,這個策略主要是為了淘汰長時間不被訪問的數(shù)據(jù); ③ expireAfterWrite:緩存項在給定時間內(nèi)沒有被寫訪問(創(chuàng)建或覆蓋),則回收, 防止舊數(shù)據(jù)被緩存過久; ④ refreshAfterWrite:緩存項在給定時間內(nèi)沒有被寫訪問(創(chuàng)建或覆蓋),則刷新; ⑤ recordStats:開啟Cache的狀態(tài)統(tǒng)計(默認是開啟的);
4.2.4 GET方法
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException { try { if (count != 0) { // read-volatile ReferenceEntry<K, V> e = getEntry(key, hash); if (e != null) { long now = map.ticker.read(); //檢查entry是否符合expireAfterAccess淘汰策略 V value = getLiveValue(e, now); // value是有效的 則返回 if (value != null) { // 記錄該值的最近訪問時間 recordRead(e, now); statsCounter.recordHits(1); // 內(nèi)部實現(xiàn)了定時刷新,若未開啟refreshAfterWrite則直接返回value return scheduleRefresh(e, key, hash, value, now, loader); } ValueReference<K, V> valueReference = e.getValueReference(); // 如果有別的線程已經(jīng)在load value,則等到其他線程完成后再取結(jié)果 if (valueReference.isLoading()) { return waitForLoadingValue(e, key, valueReference); } } } // 如果沒拿到有效的value,則執(zhí)行加載邏輯; return lockedGetOrLoad(key, hash, loader); } catch (ExecutionException ee) { ... } finally { postReadCleanup(); } }
4.2.5 Load方法
@GwtCompatible(emulated = true) public abstract class CacheLoader<K, V> { public abstract V load(K key) throws Exception; }
4.3 移除機制
guava做cache時候數(shù)據(jù)的移除分為被動移除和主動移除兩種。
- 被動移除
- 基于大小的移除:數(shù)量達到指定大小,會把不常用的鍵值移除
- 基于時間的移除:expireAfterAccess(long, TimeUnit) 根據(jù)某個鍵值對最后一次訪問之后多少時間后移除。expireAfterWrite(long, TimeUnit) 根據(jù)某個鍵值對被創(chuàng)建或值被替換后多少時間移除
- 基于引用的移除:主要是基于java的垃圾回收機制,根據(jù)鍵或者值的引用關(guān)系決定移除
- 主動移除
- 單獨移除:Cache.invalidate(key)
- 批量移除:Cache.invalidateAll(keys)
- 移除所有:Cache.invalidateAll()
如果配置了移除監(jiān)聽器RemovalListener,則在所有移除的動作時會同步執(zhí)行該listener下的邏輯。
如需改成異步,使用:RemovalListeners.asynchronous(RemovalListener, Executor)。
4.4 其他
- 在put操作之前,如果已經(jīng)有該鍵值,會先觸發(fā)removalListener移除監(jiān)聽器,再添加
- 配置了expireAfterAccess和expireAfterWrite,但在指定時間后沒有被移除。
- 刪除策略邏輯:
CacheBuilder構(gòu)建的緩存不會在特定時間自動執(zhí)行清理和回收工作,也不會在某個緩存項過期后馬上清理,它不會啟動一個線程來進行緩存維護,因為首先線程相對較重,其次某些環(huán)境限制線程的創(chuàng)建。
它會在寫操作時順帶做少量的維護工作,或者偶爾在讀操作時做。當(dāng)然,也可以創(chuàng)建自己的維護線程,以固定的時間間隔調(diào)用Cache.cleanUp()。
總結(jié)
到此這篇關(guān)于Java設(shè)置Map過期時間的的幾種方法的文章就介紹到這了,更多相關(guān)Java設(shè)置Map過期時間內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用swagger生成api接口文檔的方法詳解
在之前的文章中,使用mybatis-plus生成了對應(yīng)的包,在此基礎(chǔ)上,我們針對項目的api接口,添加swagger配置和注解,生成swagger接口文檔,需要的可以了解一下2022-10-10Java面向?qū)ο笾甪inal關(guān)鍵字詳細解讀
這篇文章主要介紹了Java面向?qū)ο笾甪inal關(guān)鍵字詳細解讀,final修飾的屬性又叫常量,一般用 XX_XX_XX來命名,final修飾的屬性在定義時必須賦初始值,并且以后不能再修改,需要的朋友可以參考下2024-01-01使用SpringBoot中web項目推薦目錄結(jié)構(gòu)的問題
這篇文章主要介紹了SpringBoot中web項目推薦目錄結(jié)構(gòu)的問題,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-01-01Spring Boot構(gòu)建優(yōu)雅的RESTful接口過程詳解
這篇文章主要介紹了spring boot構(gòu)建優(yōu)雅的RESTful接口過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-08-08packages思維及使用Java添加Android平臺特定實現(xiàn)
這篇文章主要為大家介紹了packages思維及使用Java添加Android平臺特定實現(xiàn)在Flutter框架里的體現(xiàn)和運用詳解,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12