前言
隨着互聯網的高速發展,市面上也出現了越來越多的網站和app。我們判斷一個軟件是否好用,用戶體驗就是一個重要的衡量標准。比如說我們經常用的微信,打開一個頁面要十幾秒,發個語音要幾分鍾對方才能收到。相信這樣的軟件大家肯定是都不願意用的。軟件要做到用戶體驗好,響應速度快,緩存就是必不可少的一個神器。緩存又分進程內緩存和分布式緩存兩種:分布式緩存如redis、memcached等,還有本地(進程內)緩存如ehcache、GuavaCache、Caffeine等。說起Guava Cache,很多人都不會陌生,它是Google Guava工具包中的一個非常方便易用的本地化緩存實現,基於LRU算法實現,支持多種緩存過期策略。由於Guava的大量使用,Guava Cache也得到了大量的應用。但是,Guava Cache的性能一定是最好的嗎?也許,曾經它的性能是非常不錯的。正所謂長江后浪推前浪,前浪被拍在沙灘上。我們就來介紹一個比Guava Cache性能更高的緩存框架:Caffeine。
Tips: Spring5(SpringBoot2)開始用Caffeine取代guava.詳見官方信息SPR-13797
https://jira.spring.io/browse/SPR-13797
官方性能比較
以下測試都是基於jmh測試的,官網地址
測試為什么要基於jmh測試,可以參考知乎上R回答
在HotSpot VM上跑microbenchmark切記不要在main()里跑循環計時就完事。這是典型錯誤。重要的事情重復三遍:請用JMH,請用JMH,請用JMH。除非非常了解HotSpot的實現細節,在main里這樣跑循環計時得到的結果其實對一般程序員來說根本沒有任何意義,因為無法解釋。
- 8個線程讀,100%的讀操作
- 6個線程讀,2個線程寫,也就是75%的讀操作,25%的寫操作。
- 8個線程寫,100%的寫操作
對比結論
可以從數據看出來Caffeine的性能都比Guava要好。然后Caffeine的API的操作功能和Guava是基本保持一致的,並且 Caffeine為了兼容之前是Guava的用戶,做了一個Guava的Adapter給大家使用也是十分的貼心。
如何使用
- 在 pom.xml 中添加 caffeine 依賴
<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.2</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)
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"));
運行結果如下:淘汰了兩個只剩下一個。
null
null
java金融3
基於時間(time-based)
Caffeine提供了三種定時驅逐策略:
expireAfterWrite(long, TimeUnit)
- 在最后一次寫入緩存后開始計時,在指定的時間后過期。
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金融"));
運行結果第三秒的時候取值為空。
java金融
java金融
null
expireAfterAccess
- 在最后一次讀或者寫入后開始計時,在指定的時間后過期。假如一直有請求訪問該key,那么這個緩存將一直不會過期。
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金融"));
運行結果:讀和寫都沒有的情況下,3秒后才過期,然后就輸出了null。
java金融
java金融
java金融
null
expireAfter(Expiry)
- 在expireAfter中需要自己實現Expiry接口,這個接口支持expireAfterCreate,expireAfterUpdate,以及expireAfterRead了之后多久過期。注意這個是和expireAfterAccess、expireAfterAccess是互斥的。這里和expireAfterAccess、expireAfterAccess不同的是,需要你告訴緩存框架,他應該在具體的某個時間過期,獲取具體的過期時間。
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));
刪除
- 單個刪除:Cache.invalidate(key)
- 批量刪除:Cache.invalidateAll(keys)
- 刪除所有緩存項:Cache.invalidateAll
總結
本文只是對Caffeine的一個簡單使用的介紹,它還有很多不錯的東西,比如緩存監控、事件監聽、W-TinyLFU算法(高命中率、低內存占用)感興趣的同學可以去官網查看。
結束
- 由於自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
- 如果你覺得文章還不錯,你的轉發、分享、贊賞、點贊、留言就是對我最大的鼓勵。
- 感謝您的閱讀,十分歡迎並感謝您的關注。
參考
https://www.itcodemonkey.com/article/9498.html
https://juejin.im/post/5dede1f2518825121f699339
https://www.cnblogs.com/CrankZ/p/10889859.html
https://blog.csdn.net/hy245120020/article/details/78080686
https://github.com/ben-manes/caffeine