
在工作中,加Cache是非常常見的一種性能優化手段,作業系統底層、計算機硬體層為了性能優化加了各種各樣的Cache,當然大多數都是對應用層透明的。但如果你想在應用層加Cache的話,可能就需要你自己實作了。
其實在Java環境下,Cache有各種各樣的選擇,比如最初級的你可以直接用HashMap實作一個Cache,不過你得自己關注下資料加載和淘汰的政策。更進階的有像spring-cache,代碼都不需要改,隻需要簡單加幾個注解就可以實作對關鍵資料的緩存,相當友善(後續我也會出一篇部落格介紹下spring-cache)。 今天我們要介紹的是谷歌guava包中的LoadingCache, 也是功能完善,簡單好用。
LoadingCache是Guava包中提供一個一種本地Cache,本地Cache的優勢就是沒有網絡IO,速度快。但劣勢也很明顯,Cache容量受限于本地記憶體大小,Cache中的資料沒法共享。是以它就隻适合少量熱點資料的緩存,其使用方法也很簡單,我們拿maven為例,你隻需要添加一下Maven依賴即可引入guava包:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
使用代碼也非常簡單,如下:
private static LoadingCache<String, String> cache =
CacheBuilder.newBuilder()
// 初始化容量
.initialCapacity(4)
// 緩存池大小,在緩存數量到達該大小時, Guava開始回收舊的資料
.maximumSize(8)
// 設定時間對象沒有被讀/寫通路則對象從記憶體中删除(在另外的線程裡面不定期維護)
.expireAfterAccess(5, TimeUnit.SECONDS)
// 設定緩存在寫入之後 設定時間 後失效
.expireAfterWrite(5, TimeUnit.SECONDS)
// 資料被移除時的監聽器, 緩存項被移除時會觸發執行
.removalListener((RemovalListener<String, String>) rn -> {
System.out.println(String.format("資料key:%s value:%s 因為%s被移除了", rn.getKey(), rn.getValue(),
rn.getCause().name()));
})
// 開啟Guava Cache的統計功能
.recordStats()
// 資料寫入後被多久重新整理一次
.refreshAfterWrite(5, TimeUnit.SECONDS)
// 資料并發級别
.concurrencyLevel(16)
// 當緩存中沒有資料時的資料加載器
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return key + "_" + System.currentTimeMillis();
}
});
然後我們就可以直接在代碼的其他地方用
cache.get("myKey")
來愉快地使用LoadingCache了,它會主動加載資料,并在存儲空間不夠或者資料過期時清理掉不需要的資料,非常省心且友善。
這裡有些重點參數,下面詳細介紹下:
參數 | 作用 | 注意事項 |
maximumSize | 緩存的k-v最大資料,當總緩存的資料量達到或者快達到這個值時,就會淘汰它認為不太用的一份資料,近似LRU或者LFU政策 | 并不一定是達到這個值才開始淘汰舊資料,可能接近時就會開始淘汰 |
expireAfterAccess | 資料被通路後多久就會過期,這個政策主要是為了淘汰長時間不被通路的資料 | 資料過期不是立即淘汰,而是有資料通路時才會觸發 |
expireAfterWrite | 資料寫入後多久過期,這個政策是為了防止舊資料被緩存過久 | 同上 |
refreshAfterWrite | 資料寫入後多久重新整理一次,這個類似于expireAfterWrite,但它會主動更新資料 | 同上 |
concurrencyLevel | 資料的并發級别,LoadingCache為了實作線程安全,它裡面采用了類似Java7中ConcurrentHashMap的實作,采用了分段加鎖的方式,分段數影響了它的最大并發量 | |
recordStats | 開啟Cache的狀态統計(預設是開啟的) | 開啟這個是會影響到性能的,如果要求極緻性能的話關注下個 |
我們來重點介紹下CacheLoader CacheStats和RemovalListener,因為這三者涉及到了資料的加載、使用和删除的完整生命周期,先來看下CacheLoader。
CacheLoader
CacheLoader的作用就是為了在Cache中資料缺失時加載資料,其中最重要的方法就是load()方法,你可以在load() 方法中實作對應key加載資料的邏輯。在調用LoadingCache的get(key)方法時,如果key對應的value不存在,LoadingCache就會調起你在建立cache時傳入的CacheLoader的load方法。
CacheStats
使用
CacheStats cacheStats = cache.stats();
我就可以擷取到cache的stats資料。從cacheStats中我們可以看到cache的命中率、命中數、異常率、加載時延……等資料,通過這些資料就可以直覺地看出我們cache的一些性能名額,如果做出一些參數調整。 比如如果命中率過低,我們是不是可以調整大下maximumSize,或者調整下資料的過期政策?
RemovalListener
RemovalListener會在LoadingCache中資料被清理時調起,其實就是個監聽器模式,這樣你可以通過Listener實作對資料淘汰事件的監聽,比如在資料淘汰時打一行日志啥的。使用方法也很簡單,在Java8+上你可以直接使用lambda表達式,或者也可以自己實作RemovalListener接口,并在建構Cache時注冊進去即可。
public enum RemovalCause {
EXPLICIT {
@Override
boolean wasEvicted() {
return false;
}
},
REPLACED {
@Override
boolean wasEvicted() {
return false;
}
},
COLLECTED {
@Override
boolean wasEvicted() {
return true;
}
},
EXPIRED {
@Override
boolean wasEvicted() {
return true;
}
},
SIZE {
@Override
boolean wasEvicted() {
return true;
}
};
abstract boolean wasEvicted();
}