一文詳解Caffeine緩存庫的常用功能與使用指南
Caffeine作為新一代高性能Java緩存庫,在并發(fā)場景下展現(xiàn)出卓越表現(xiàn)。它通過創(chuàng)新的W-TinyLFU淘汰算法實(shí)現(xiàn)高達(dá)99%的命中率,并采用無鎖設(shè)計(jì)使吞吐量較傳統(tǒng)方案提升5-10倍。該庫提供靈活的緩存管理能力:支持基于時(shí)間(寫入/訪問過期)、數(shù)量或權(quán)重的淘汰策略;允許為單個(gè)Key設(shè)置專屬過期時(shí)間;獨(dú)創(chuàng)的異步刷新機(jī)制能在不阻塞請(qǐng)求的情況下更新數(shù)據(jù)。開發(fā)者可通過簡潔的鏈?zhǔn)紸PI配置內(nèi)存控制、加載邏輯和事件監(jiān)聽,輕松構(gòu)建高并發(fā)低延遲的智能緩存系統(tǒng)。其與Guava Cache兼容的接口設(shè)計(jì),更使遷移成本降至最低。
以下是 Caffeine 緩存庫的常用功能使用介紹,涵蓋基礎(chǔ)操作、過期策略、淘汰配置等核心功能:
一、基礎(chǔ)緩存操作
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
// 1. 創(chuàng)建緩存實(shí)例
Cache<String, Object> cache = Caffeine.newBuilder()
.build();
// 2. 添加數(shù)據(jù)
cache.put("key1", "value1");
// 3. 獲取數(shù)據(jù)(手動(dòng))
Object value = cache.getIfPresent("key1"); // 存在返回value,否則null
// 4. 刪除數(shù)據(jù)
cache.invalidate("key1");
cache.invalidateAll(); // 清空緩存
二、自動(dòng)加載緩存(推薦)
LoadingCache<String, Object> cache = Caffeine.newBuilder()
.build(key -> {
// 緩存未命中時(shí)自動(dòng)執(zhí)行的加載邏輯
return fetchFromDB(key); // 自定義數(shù)據(jù)庫加載方法
});
// 自動(dòng)加載數(shù)據(jù)(緩存未命中時(shí)執(zhí)行build中的邏輯)
Object value = cache.get("user_123");
三、過期策略配置
1. 全局過期策略
Cache<String, Object> cache = Caffeine.newBuilder()
// 寫入后30分鐘過期
.expireAfterWrite(30, TimeUnit.MINUTES)
// 最后訪問后15分鐘過期
.expireAfterAccess(15, TimeUnit.MINUTES)
// 自定義過期策略(按需實(shí)現(xiàn))
.expireAfter(new Expiry<String, Object>() {
public long expireAfterCreate(String key, Object value, long currentTime) {
return TimeUnit.MINUTES.toNanos(10); // 創(chuàng)建后10分鐘過期
}
public long expireAfterUpdate(String key, Object value, long currentTime, long currentDuration) {
return currentDuration; // 更新后不改變過期時(shí)間
}
public long expireAfterRead(String key, Object value, long currentTime, long currentDuration) {
return currentDuration; // 讀取后不改變過期時(shí)間
}
})
.build();
2. 單Key過期(高級(jí)用法)
// 創(chuàng)建支持變長過期的緩存
Cache<String, Object> cache = Caffeine.newBuilder()
.expireAfter(new Expiry<String, Object>() {
// ...實(shí)現(xiàn)同上
})
.build();
// 為特定Key設(shè)置不同過期時(shí)間
cache.policy().expireVariably().ifPresent(policy -> {
policy.put("hot_key", "value", 2, TimeUnit.HOURS); // 2小時(shí)
policy.put("cold_key", "value", 10, TimeUnit.MINUTES); // 10分鐘
});
四、淘汰策略配置
Cache<String, Object> cache = Caffeine.newBuilder()
// 基于數(shù)量淘汰(最多1000個(gè)條目)
.maximumSize(1000)
// 基于權(quán)重淘汰(需實(shí)現(xiàn)Weigher)
.maximumWeight(10_000)
.weigher((String key, Object value) -> {
// 自定義權(quán)重計(jì)算邏輯
if (value instanceof String) return ((String) value).length();
if (value instanceof List) return ((List<?>) value).size();
return 1;
})
// 基于引用回收(謹(jǐn)慎使用)
.weakKeys() // 弱引用Key
.weakValues() // 弱引用Value
.softValues() // 軟引用Value
.build();
五、刷新策略(優(yōu)于純過期)
LoadingCache<String, Object> cache = Caffeine.newBuilder()
// 寫入后1分鐘可刷新(不阻塞讀取,異步刷新舊值)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(key -> fetchFromDB(key));
六、監(jiān)聽器與統(tǒng)計(jì)
Cache<String, Object> cache = Caffeine.newBuilder()
// 移除監(jiān)聽器
.removalListener((String key, Object value, RemovalCause cause) -> {
System.out.printf("Key %s was removed (%s)%n", key, cause);
})
// 啟用統(tǒng)計(jì)
.recordStats()
.build();
// 獲取統(tǒng)計(jì)信息
CacheStats stats = cache.stats();
System.out.printf("Hit Rate: %.2f%%, Loads: %d%n",
stats.hitRate() * 100, stats.loadCount());
七、異步操作
// 1. 異步緩存
AsyncLoadingCache<String, Object> asyncCache = Caffeine.newBuilder()
.buildAsync(key -> fetchFromDB(key));
// 獲取數(shù)據(jù)(返回CompletableFuture)
CompletableFuture<Object> future = asyncCache.get("key1");
// 2. 同步視圖操作
Object value = asyncCache.synchronous().get("key1");
八、最佳實(shí)踐配置模板
LoadingCache<String, Object> optimalCache = Caffeine.newBuilder()
// 容量控制
.maximumSize(10_000)
// 過期策略
.expireAfterWrite(30, TimeUnit.MINUTES)
// 刷新策略
.refreshAfterWrite(5, TimeUnit.MINUTES)
// 統(tǒng)計(jì)和監(jiān)聽
.recordStats()
.removalListener((key, value, cause) ->
logRemoval(key, cause))
// 自動(dòng)加載
.build(key -> fetchFromDB(key));
九、基于Caffeine實(shí)現(xiàn)的動(dòng)態(tài)緩存
我們有時(shí)候需要這樣一種場景:當(dāng)用戶請(qǐng)求某個(gè)key的時(shí)候,該緩存自動(dòng)從數(shù)據(jù)庫去加載,就是注冊(cè)一個(gè)數(shù)據(jù)庫加載器(自己實(shí)現(xiàn)),當(dāng)獲取不到該key時(shí),自動(dòng)走數(shù)據(jù)庫查詢,然后存入該key中。當(dāng)往caffeine緩存中插入一個(gè)key后,如果緩存沒有,則自動(dòng)存入,并自動(dòng)同步到數(shù)據(jù)庫中,當(dāng)刪除一個(gè)key,或key過期后,自動(dòng)從數(shù)據(jù)庫同步刪除。
以下是簡單的實(shí)現(xiàn)流程:
import com.github.benmanes.caffeine.cache.*;
import java.util.concurrent.TimeUnit;
public class DynamicCache<K, V> {
private final Cache<K, V> cache;
private final DataLoader<K, V> dataLoader;
private final DataSynchronizer<K, V> dataSynchronizer;
public DynamicCache(DataLoader<K, V> dataLoader, DataSynchronizer<K, V> dataSynchronizer) {
this.dataLoader = dataLoader;
this.dataSynchronizer = dataSynchronizer;
this.cache = Caffeine.newBuilder()
// 配置緩存策略(按需設(shè)置)
.expireAfterWrite(30, TimeUnit.MINUTES) // 30分鐘過期
.maximumSize(1000) // 最大緩存項(xiàng)
// 注冊(cè)移除監(jiān)聽器(用于刪除數(shù)據(jù)庫數(shù)據(jù))
.removalListener((K key, V value, RemovalCause cause) -> {
if (cause.wasEvicted()) { // 僅處理過期或容量剔除
dataSynchronizer.deleteFromDatabase(key);
}
})
// 注冊(cè)加載器(用于緩存未命中時(shí)從DB加載)
.build(key -> {
V value = dataLoader.loadFromDatabase(key);
if (value == null) throw new Exception("Key not found");
return value;
});
}
// 獲取數(shù)據(jù)(自動(dòng)加載)
public V get(K key) {
return cache.get(key, k -> {
V value = dataLoader.loadFromDatabase(k);
if (value == null) throw new RuntimeException("Data not found");
return value;
});
}
// 添加/更新數(shù)據(jù)(同步到數(shù)據(jù)庫)
public void put(K key, V value) {
// 先同步到數(shù)據(jù)庫
dataSynchronizer.saveToDatabase(key, value);
// 再更新緩存
cache.put(key, value);
}
// 刪除數(shù)據(jù)(同步刪除數(shù)據(jù)庫)
public void delete(K key) {
// 先刪除數(shù)據(jù)庫數(shù)據(jù)
dataSynchronizer.deleteFromDatabase(key);
// 再使緩存失效
cache.invalidate(key);
}
// 數(shù)據(jù)庫加載器接口
public interface DataLoader<K, V> {
V loadFromDatabase(K key);
}
// 數(shù)據(jù)庫同步器接口
public interface DataSynchronizer<K, V> {
void saveToDatabase(K key, V value);
void deleteFromDatabase(K key);
}
}
上述接口使用示例:
// 1. 實(shí)現(xiàn)數(shù)據(jù)庫操作接口
DynamicCache.DataLoader<String, User> loader = key ->
jdbcTemplate.queryForObject("SELECT * FROM users WHERE id=?", User.class, key);
DynamicCache.DataSynchronizer<String, User> synchronizer = new DynamicCache.DataSynchronizer<>() {
@Override
public void saveToDatabase(String key, User value) {
jdbcTemplate.update("INSERT OR REPLACE INTO users (id, name) VALUES (?, ?)",
key, value.getName());
}
@Override
public void deleteFromDatabase(String key) {
jdbcTemplate.update("DELETE FROM users WHERE id=?", key);
}
};
// 2. 創(chuàng)建緩存實(shí)例
DynamicCache<String, User> userCache = new DynamicCache<>(loader, synchronizer);
// 3. 使用緩存
// 自動(dòng)加載(緩存未命中時(shí)從DB加載)
User user = userCache.get("user123");
// 添加/更新(同步到DB)
userCache.put("user456", new User("Alice"));
// 刪除(同步刪除DB數(shù)據(jù))
userCache.delete("user789");
關(guān)鍵特性說明:
1.自動(dòng)加載:
- 當(dāng)調(diào)用
get()方法且緩存未命中時(shí),自動(dòng)通過DataLoader從數(shù)據(jù)庫加載 - 加載成功后自動(dòng)填充緩存
2.寫穿透:
put() 操作時(shí):
- 先通過
DataSynchronizer保存到數(shù)據(jù)庫 - 再更新緩存
保證數(shù)據(jù)庫與緩存的數(shù)據(jù)一致性
3.刪除同步:
delete() 操作時(shí):
- 先刪除數(shù)據(jù)庫數(shù)據(jù)
- 再使緩存失效
緩存過期/淘汰時(shí):
通過 RemovalListener 自動(dòng)觸發(fā)數(shù)據(jù)庫刪除
4.緩存配置:
- 可自定義過期時(shí)間(
expireAfterWrite) - 可設(shè)置最大容量(
maximumSize) - 支持其他Caffeine特性(刷新策略、弱引用等)
關(guān)鍵特性對(duì)比表
| 功能 | 配置方法 | 適用場景 |
|---|---|---|
| 寫入過期 | expireAfterWrite() | 數(shù)據(jù)更新頻率低的場景 |
| 訪問過期 | expireAfterAccess() | 讀多寫少的場景 |
| 自適應(yīng)過期 | expireAfter(Expiry) | 需要?jiǎng)討B(tài)過期時(shí)間的場景 |
| 數(shù)量淘汰 | maximumSize() | 通用場景 |
| 權(quán)重淘汰 | maximumWeight() + weigher() | 緩存對(duì)象大小差異大的場景 |
| 異步刷新 | refreshAfterWrite() | 高并發(fā)讀取+后臺(tái)更新 |
| 弱/軟引用 | weakKeys()/softValues() | 內(nèi)存敏感型應(yīng)用 |
注意事項(xiàng)
1.刷新 vs 過期:
- 刷新 (
refreshAfterWrite) 異步更新舊值,不阻塞請(qǐng)求 - 過期 (
expireAfterWrite) 會(huì)阻塞請(qǐng)求直到新值加載完成
2.權(quán)重計(jì)算:
- 確保
weigher計(jì)算快速(納秒級(jí)) - 權(quán)重總和不超過
maximumWeight
3.過期時(shí)間精度:
.scheduler(Scheduler.systemScheduler())
默認(rèn)時(shí)間精度≈1秒,需要毫秒級(jí)精度可配置:
4.并發(fā)加載控制:
- 相同key并發(fā)請(qǐng)求時(shí),只有一個(gè)線程執(zhí)行加載
- 可通過
executor()指定自定義線程池
到此這篇關(guān)于一文詳解Caffeine緩存庫的常用功能與使用指南的文章就介紹到這了,更多相關(guān)Caffeine緩存庫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring mvc中注解@ModelAttribute的妙用分享
這篇文章主要給大家介紹了關(guān)于spring mvc中注解@ModelAttribute妙用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Android具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-09-09
淺談spring的重試機(jī)制無效@Retryable@EnableRetry
這篇文章主要介紹了淺談spring的重試機(jī)制無效@Retryable@EnableRetry,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java遞歸算法詳解(動(dòng)力節(jié)點(diǎn)整理)
Java遞歸算法是基于Java語言實(shí)現(xiàn)的遞歸算法。遞歸算法對(duì)解決一大類問題很有效,它可以使算法簡潔和易于理解。接下來通過本文給大家介紹Java遞歸算法相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧2017-03-03
java 單元測試 對(duì)h2數(shù)據(jù)庫數(shù)據(jù)清理方式
這篇文章主要介紹了java 單元測試 對(duì)h2數(shù)據(jù)庫數(shù)據(jù)清理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Java如何使用spire進(jìn)行word文檔的替換詳解
創(chuàng)作一份文案經(jīng)常會(huì)高頻率地使用某些詞匯,如地名、人名、人物職位等,若表述有誤,就需要整體撤換,下面這篇文章主要給大家介紹了關(guān)于Java如何使用spire進(jìn)行word文檔的替換的相關(guān)資料,需要的朋友可以參考下2023-01-01
一文帶你掌握J(rèn)ava8中函數(shù)式接口的使用和自定義
函數(shù)式接口是?Java?8?引入的一種接口,用于支持函數(shù)式編程,下面我們就來深入探討函數(shù)式接口的概念、用途以及如何創(chuàng)建和使用函數(shù)式接口吧2023-08-08
解決spring-data-jpa 事物中修改屬性自動(dòng)更新update問題
這篇文章主要介紹了解決spring-data-jpa 事物中修改屬性自動(dòng)更新update問題,具有很好的參考價(jià)值,希望對(duì)大家2021-08-08

