DB + Redis + LocalCache = 高效存儲,高效訪問
Caffeine簡介
Caffeine是基於Java 8的高性能,接近最佳的緩存工具庫。Caffeine使用Google Guava啟發的API提供內存緩存。所以它的使用成本較低,跟Guava的API基本一致。
它主要有以下幾個功能:
- 自動將條目加載到緩存中,可以選擇同步或異步加載
- 基於頻率和新近度超過最大值時基於大小的逐出
- 自上次訪問或上次寫入以來測得的基於時間的條目到期
- 發生第一個陳舊的條目請求時,異步刷新
- 鍵自動包裝在弱引用中
- 值自動包裝在弱引用或軟引用中
- 逐出(或以其他方式刪除)條目的通知
- 寫入通知
- 緩存訪問統計信息的
快速入門
Cache分為LoadingCache(同步緩存),AsyncLoadingCache(異步緩存)。
pom 依賴
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.5</version>
</dependency>
創建對象
Cache<String, Object> cache = Caffeine.newBuilder()
.initialCapacity(100)//初始大小
.maximumSize(200)//最大數量
.expireAfterWrite(3, TimeUnit.SECONDS)//過期時間
.build();
參數介紹:
- initialCapacity:初始的緩存空間大小
- maximumSize:緩存的最大數量
- maximumWeight:緩存的最大權重
- expireAfterAccess:最后一次讀或寫操作后經過指定時間過期
- expireAfterWrite:最后一次寫操作后經過指定時間過期
- refreshAfterWrite:創建緩存或者最近一次更新緩存后經過指定時間間隔,刷新緩存
- weakKeys:打開key的弱引用
- weakValues:打開value的弱引用
- softValues:打開value的軟引用
- recordStats:開發統計功能
注意:
- expireAfterWrite和expireAfterAccess同時存在時,以expireAfterWrite為准。
- maximumSize和maximumWeight不可以同時使用。
添加數據
Caffeine 為我們提供了手動、同步和異步這幾種填充策略。
Cache<String, String> cache = Caffeine.newBuilder()
.build();
cache.put("java金融", "java金融");
System.out.println(cache.getIfPresent("java金融"));
自動添加(自定義添加函數)
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.build();
// 1.如果緩存中能查到,則直接返回
// 2.如果查不到,則從我們自定義的getValue方法獲取數據,並加入到緩存中
String val = cache.get("java金融", k -> getValue(k));
System.out.println(val);
}
/**
* 緩存中找不到,則會進入這個方法。一般是從數據庫獲取內容
*
* @param k
* @return
*/
private static String getValue(String k) {
return k + ":value";
}
過期策略
Caffeine 為我們提供了三種過期策略,分別是基於大小(size-based)、基於時間(time-based)、基於引用(reference-based)
基於大小(size-based)
public static void main(String[] args) {
LoadingCache<String, String> cache = Caffeine.newBuilder()
// 最大容量為1
.maximumSize(1)
.build(k -> getValue(k));
cache.put("java金融1", "java金融1");
cache.put("java金融2", "java金融2");
cache.put("java金融3", "java金融3");
cache.cleanUp();
System.out.println(cache.getIfPresent("java金融1"));
System.out.println(cache.getIfPresent("java金融2"));
System.out.println(cache.getIfPresent("java金融3"));
}
/**
* * 緩存中找不到,則會進入這個方法。一般是從數據庫獲取內容
* * @param k
* * @return
*/
private static String getValue(String k) {
return k + ":value";
}
運行結果如下:淘汰了兩個只剩下一個。
null
null
java金融3
基於時間(time-based)
Caffeine提供了三種定時驅逐策略:
expireAfterWrite(long, TimeUnit)
在最后一次寫入緩存后開始計時,在指定的時間后過期。
public static void main(String[] args) throws Exception{
LoadingCache<String, String> cache = Caffeine.newBuilder()
// 最大容量為1
.maximumSize(1)
.expireAfterWrite(3, TimeUnit.SECONDS)
.build(k->getValue(k));
cache.put("java金融","java金融");
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("java金融"));
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("java金融"));
Thread.sleep(1*1000);
System.out.println(cache.getIfPresent("java金融"));
}
/**
* * 緩存中找不到,則會進入這個方法。一般是從數據庫獲取內容
* * @param k
* * @return
*/
private static String getValue(String k) {
return k + ":value";
}
運行結果第三秒的時候取值為空:
java金融
java金融
null
expireAfterAccess
在最后一次讀或者寫入后開始計時,在指定的時間后過期。假如一直有請求訪問該key,那么這個緩存將一直不會過期。
public static void main(String[] args) throws Exception {
LoadingCache<String, String> cache = Caffeine.newBuilder()
// 最大容量為1
.maximumSize(1)
.expireAfterAccess(3, TimeUnit.SECONDS)
.build(k -> getValue(k));
cache.put("java金融", "java金融");
Thread.sleep(1 * 1000);
System.out.println(cache.getIfPresent("java金融"));
Thread.sleep(1 * 1000);
System.out.println(cache.getIfPresent("java金融"));
Thread.sleep(1 * 1000);
System.out.println(cache.getIfPresent("java金融"));
Thread.sleep(3001);
System.out.println(cache.getIfPresent("java金融"));
}
/**
* * 緩存中找不到,則會進入這個方法。一般是從數據庫獲取內容
* * @param k
* * @return
*/
private static String getValue(String k) {
return k + ":value";
}
運行結果:讀和寫都沒有的情況下,3秒后才過期,然后就輸出了null。
java金融
java金融
java金融
null
expireAfter(Expiry)
在expireAfter中需要自己實現Expiry接口,這個接口支持expireAfterCreate,expireAfterUpdate,以及expireAfterRead了之后多久過期。注意這個是和expireAfterAccess、expireAfterAccess是互斥的。這里和expireAfterAccess、expireAfterAccess不同的是,需要你告訴緩存框架,它應該在具體的某個時間過期,獲取具體的過期時間。
public static void main(String[] args) throws Exception {
LoadingCache<String, String> cache = Caffeine.newBuilder()
// 最大容量為1
.maximumSize(1)
.removalListener((key, value, cause) ->
System.out.println("key:" + key + ",value:" + value + ",刪除原因:" + cause))
.expireAfter(new Expiry<String, String>() {
@Override
public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) {
return currentTime;
}
@Override
public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
return currentTime;
}
@Override
public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
return currentTime;
}
})
.build(k -> getValue(k));
}
/**
* * 緩存中找不到,則會進入這個方法。一般是從數據庫獲取內容
* * @param k
* * @return
*/
private static String getValue(String k) {
return k + ":value";
}
刪除
- 單個刪除:Cache.invalidate(key)
- 批量刪除:Cache.invalidateAll(keys)
- 刪除所有緩存項:Cache.invalidateAll