本地緩存高性能之王Caffeine

前言

隨着互聯網的高速發展,市面上也出現了愈來愈多的網站和app。咱們判斷一個軟件是否好用,用戶體驗就是一個重要的衡量標準。好比說咱們常常用的微信,打開一個頁面要十幾秒,發個語音要幾分鐘對方纔能收到。相信這樣的軟件你們確定是都不肯意用的。軟件要作到用戶體驗好,響應速度快,緩存就是必不可少的一個神器。緩存又分進程內緩存和分佈式緩存兩種:分佈式緩存如redis、memcached等,還有本地(進程內)緩存如ehcache、GuavaCache、Caffeine等。提及Guava Cache,不少人都不會陌生,它是Google Guava工具包中的一個很是方便易用的本地化緩存實現,基於LRU算法實現,支持多種緩存過時策略。因爲Guava的大量使用,Guava Cache也獲得了大量的應用。可是,Guava Cache的性能必定是最好的嗎?也許,曾經它的性能是很是不錯的。正所謂長江後浪推前浪,前浪被拍在沙灘上。咱們就來介紹一個比Guava Cache性能更高的緩存框架:Caffeinehtml

Tips: Spring5(SpringBoot2)開始用Caffeine取代guava.詳見官方信息SPR-13797
https://jira.spring.io/browse...

官方性能比較

如下測試都是基於jmh測試的,官網地址
測試爲何要基於jmh測試,能夠參考知乎上R回答java

在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給你們使用也是十分的貼心。git

如何使用

  • 在 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:開發統計功能github

    注意:

expireAfterWrite和expireAfterAccess同時存在時,以expireAfterWrite爲準。maximumSize和maximumWeight不能夠同時使用。redis

添加數據

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)spring

基於大小(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算法(高命中率、低內存佔用)感興趣的同窗能夠去官網查看。

結束

  • 因爲本身才疏學淺,不免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
  • 若是你以爲文章還不錯,你的轉發、分享、讚揚、點贊、留言就是對我最大的鼓勵。
  • 感謝您的閱讀,十分歡迎並感謝您的關注。
  • 1589987759.png

參考

https://www.itcodemonkey.com/...
https://juejin.im/post/5dede1...
https://www.cnblogs.com/Crank...
https://blog.csdn.net/hy24512...
https://github.com/ben-manes/...

相關文章
相關標籤/搜索