Caffeine使用指南
Caffeine基於java8的高性能,接近最優的緩存庫。Caffeine提供的內存緩存使用參考Google guava的API。Caffeine是基於Google guava和 ConcurrentLinkedHashMap的設計經驗上改進的成果。
Caffeine可以通過建造者模式靈活的組合以下特性:
- 通過異步自動加載實體到緩存中
- 基於大小的回收策略
- 基於時間的回收策略
- 自動刷新
- key自動封裝虛引用
- value自動封裝弱引用或軟引用
- 實體過期或被刪除的通知
- 寫入外部資源
- 統計累計訪問緩存
加載策略
Caffeine提供了3種加載策略:手動加載,同步加載,異步加載
手動加載
Cache<Key, Graph> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
// 檢索一個entry,如果沒有則爲null
Graph graph = cache.getIfPresent(key);
// 檢索一個entry,如果entry爲null,則通過key創建一個entry並加入緩存
graph = cache.get(key, k -> createExpensiveGraph(key));
// 插入或更新一個實體
cache.put(key, graph);
// 移除一個實體
cache.invalidate(key);
同步加載
構造Cache時候,build方法傳入一個CacheLoader實現類。實現load方法,通過key加載value。
LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
//如果緩存種沒有對應的value,通過createExpensiveGraph方法加載
Graph graph = cache.get(key);
Map<Key, Graph> graphs = cache.getAll(keys);
異步加載
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
CompletableFuture<Graph> graph = cache.get(key);
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);
AsyncLoadingCache 是 LoadingCache 的變體, 可以異步計算實體在一個線程池(Executor)上並且返回 CompletableFuture.
回收策略
Caffeine提供了3種回收策略:基於大小回收,基於時間回收,基於引用回收
基於大小回收
// 基於實體數量淘汰實體
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.build(key -> createExpensiveGraph(key));
// 通過權重來計算,每個實體都有不同的權重,總權重到達最高時淘汰實體。
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumWeight(10_000)
.weigher((Key key, Graph graph) -> graph.vertices().size())
.build(key -> createExpensiveGraph(key));
到達最大大小時淘汰最近最少使用的實體
基於時間回收
- 實體被訪問之後,在實體被讀或被寫後的一段時間後過期
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
- 基於寫之後,在實體被寫入後的一段時間後過期
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
- 自定義策略Expiry,可以自定義在實體被讀,被更新,被創建後的時間過期。
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfter(new Expiry<Key, Graph>() {
public long expireAfterCreate(Key key, Graph graph, long currentTime) {
// Use wall clock time, rather than nanotime, if from an external resource
long seconds = graph.creationDate().plusHours(5)
.minus(System.currentTimeMillis(), MILLIS)
.toEpochSecond();
return TimeUnit.SECONDS.toNanos(seconds);
}
public long expireAfterUpdate(Key key, Graph graph,
long currentTime, long currentDuration) {
return currentDuration;
}
public long expireAfterRead(Key key, Graph graph,
long currentTime, long currentDuration) {
return currentDuration;
}
})
.build(key -> createExpensiveGraph(key));
基於引用回收
java種有四種引用:強引用,軟引用,弱引用和虛引用,caffeine可以將值封裝成弱引用或軟引用。
軟引用:如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。
弱引用:弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.weakKeys()
.weakValues()
.build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.softValues()
.build(key -> createExpensiveGraph(key));
自動刷新
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
在寫後的持續時間過後,調用createExpensiveGraph刷新
移除通知
Cache<Key, Graph> graphs = Caffeine.newBuilder()
.removalListener((Key key, Graph graph, RemovalCause cause) ->
System.out.printf("Key %s was removed (%s)%n", key, cause))
.build();
通過removalListener添加實體移除監聽器
寫到外部存儲
通過CacheWriter 可以將緩存回寫的外部存儲中。
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.writer(new CacheWriter<Key, Graph>() {
@Override public void write(Key key, Graph graph) {
// 寫入到外部存儲或二級緩存
}
@Override public void delete(Key key, Graph graph, RemovalCause cause) {
// 刪除外部存儲或者二級緩存
}
})
.build(key -> createExpensiveGraph(key));
使用場景
- 緩存同步數據庫
- 多級緩存同步
注意,CacheWriter不能與弱鍵或AsyncLoadingCache一起使用
統計緩存使用情況
Cache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.recordStats()
.build();
通過使用Caffeine.recordStats(), 可以轉化成一個統計的集合. 通過 Cache.stats() 返回一個CacheStats。CacheStats提供以下統計方法
hitRate(): 返回緩存命中率
evictionCount(): 緩存回收數量
averageLoadPenalty(): 加載新值的平均時間