隨著網際網路的高速發展,市面上也出現了越來越多的網站和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-13797https://jira.spring.io/browse/SPR-13797
以下測試都是基於jmh測試的,官網地址測試為什麼要基於jmh測試,可以參考知乎上R回答
在HotSpot VM上跑microbenchmark切記不要在main()裡跑循環計時就完事。這是典型錯誤。重要的事情重複三遍:請用JMH,請用JMH,請用JMH。除非非常了解HotSpot的實現細節,在main裡這樣跑循環計時得到的結果其實對一般程式設計師來說根本沒有任何意義,因為無法解釋。
可以從數據看出來Caffeine的性能都比Guava要好。然後Caffeine的API的操作功能和Guava是基本保持一致的,並且 Caffeine為了兼容之前是Guava的用戶,做了一個Guava的Adapter給大家使用也是十分的貼心。
1<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->2<dependency>3 <groupId>com.github.ben-manes.caffeine</groupId>4 <artifactId>caffeine</artifactId>5 <version>2.8.2</version>6</dependency>
1 Cache<String, Object> cache = Caffeine.newBuilder()2 .initialCapacity(100)//初始大小3 .maximumSize(200)//最大數量4 .expireAfterWrite(3, TimeUnit.SECONDS)//過期時間5 .build();
創建參數介紹
注意:expireAfterWrite和expireAfterAccess同時存在時,以expireAfterWrite為準。maximumSize和maximumWeight不可以同時使用。
Caffeine 為我們提供了手動、同步和異步這幾種填充策略。下面我們來演示下手動填充策略吧,其他幾種如果大家感興趣的可以去官網了解下
1 Cache<String, String> cache = Caffeine.newBuilder()2 .build();3 cache.put("java金融", "java金融");4 System.out.println(cache.getIfPresent("java金融"));
1 public static void main(String[] args) { 2 Cache<String, String> cache = Caffeine.newBuilder() 3 .build(); 4 // 1.如果緩存中能查到,則直接返回 5 // 2.如果查不到,則從我們自定義的getValue方法獲取數據,並加入到緩存中 6 String val = cache.get("java金融", k -> getValue(k)); 7 System.out.println(val); 8 } 9 /**10 * 緩存中找不到,則會進入這個方法。一般是從資料庫獲取內容11 * @param k12 * @return13 */14 private static String getValue(String k) {15 return k + ":value";16 }
Caffeine 為我們提供了三種過期策略,分別是基於大小(size-based)、基於時間(time-based)、基於引用(reference-based)
1 LoadingCache<String, String> cache = Caffeine.newBuilder() 2 // 最大容量為1 3 .maximumSize(1) 4 .build(k->getValue(k)); 5 cache.put("java金融1","java金融1"); 6 cache.put("java金融2","java金融2"); 7 cache.put("java金融3","java金融3"); 8 cache.cleanUp(); 9 System.out.println(cache.getIfPresent("java金融1"));10 System.out.println(cache.getIfPresent("java金融2"));11 System.out.println(cache.getIfPresent("java金融3"));
運行結果如下:淘汰了兩個只剩下一個。
1null2null3java金融3
Caffeine提供了三種定時驅逐策略:
1 LoadingCache<String, String> cache = Caffeine.newBuilder() 2 // 最大容量為1 3 .maximumSize(1) 4 .expireAfterWrite(3, TimeUnit.SECONDS) 5 .build(k->getValue(k)); 6 cache.put("java金融","java金融"); 7 Thread.sleep(1*1000); 8 System.out.println(cache.getIfPresent("java金融")); 9 Thread.sleep(1*1000);10 System.out.println(cache.getIfPresent("java金融"));11 Thread.sleep(1*1000);12 System.out.println(cache.getIfPresent("java金融"));
運行結果第三秒的時候取值為空。
1java金融2java金融3null
1LoadingCache<String, String> cache = Caffeine.newBuilder() 2 // 最大容量為1 3 .maximumSize(1) 4 .expireAfterAccess(3, TimeUnit.SECONDS) 5 .build(k->getValue(k)); 6 cache.put("java金融","java金融"); 7 Thread.sleep(1*1000); 8 System.out.println(cache.getIfPresent("java金融")); 9 Thread.sleep(1*1000);10 System.out.println(cache.getIfPresent("java金融"));11 Thread.sleep(1*1000);12 System.out.println(cache.getIfPresent("java金融"));13 Thread.sleep(3001);14 System.out.println(cache.getIfPresent("java金融"));
運行結果:讀和寫都沒有的情況下,3秒後才過期,然後就輸出了null。
1java金融2java金融3java金融4null
1 LoadingCache<String, String> cache = Caffeine.newBuilder() 2 // 最大容量為1 3 .maximumSize(1) 4 .removalListener((key, value, cause) -> 5 System.out.println("key:" + key + ",value:" + value + ",刪除原因:" + cause)) 6 .expireAfter(new Expiry<String, String>() { 7 @Override 8 public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) { 9 return currentTime;10 }11 @Override12 public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {13 return currentTime;14 }1516 @Override17 public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {18 return currentTime;19 }20 })21 .build(k -> getValue(k));
本文只是對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