本地緩存高性能之王Caffeine

2020-11-14 碼農二胖

前言

隨著網際網路的高速發展,市面上也出現了越來越多的網站和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裡這樣跑循環計時得到的結果其實對一般程式設計師來說根本沒有任何意義,因為無法解釋。

  • 8個線程讀,100%的讀操作
  • 6個線程讀,2個線程寫,也就是75%的讀操作,25%的寫操作。
  • 8個線程寫,100%的寫操作

對比結論

可以從數據看出來Caffeine的性能都比Guava要好。然後Caffeine的API的操作功能和Guava是基本保持一致的,並且 Caffeine為了兼容之前是Guava的用戶,做了一個Guava的Adapter給大家使用也是十分的貼心。

如何使用

  • 在 pom.xml 中添加 caffeine 依賴

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();

創建參數介紹

  • initialCapacity: 初始的緩存空間大小
  • maximumSize: 緩存的最大數量
  • maximumWeight: 緩存的最大權重
  • expireAfterAccess: 最後一次讀或寫操作後經過指定時間過期
  • expireAfterWrite: 最後一次寫操作後經過指定時間過期
  • refreshAfterWrite: 創建緩存或者最近一次更新緩存後經過指定時間間隔,刷新緩存
  • weakKeys: 打開key的弱引用
  • weakValues:打開value的弱引用
  • softValues:打開value的軟引用
  • recordStats:開發統計功能

注意: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)

基於大小(size-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

基於時間(time-based)

Caffeine提供了三種定時驅逐策略:

expireAfterWrite(long, TimeUnit)

  • 在最後一次寫入緩存後開始計時,在指定的時間後過期。

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

expireAfterAccess

  • 在最後一次讀或者寫入後開始計時,在指定的時間後過期。假如一直有請求訪問該key,那麼這個緩存將一直不會過期。

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

expireAfter(Expiry)

  • 在expireAfter中需要自己實現Expiry接口,這個接口支持expireAfterCreate,expireAfterUpdate,以及expireAfterRead了之後多久過期。注意這個是和expireAfterAccess、expireAfterAccess是互斥的。這裡和expireAfterAccess、expireAfterAccess不同的是,需要你告訴緩存框架,他應該在具體的某個時間過期,獲取具體的過期時間。

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

刪除

  • 單個刪除: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

相關焦點

  • Caffeine Cache~高性能 Java 本地緩存之王
    使用註解來對 cache 增刪改查前面剛說到Guava Cache,他的優點是封裝了get,put操作;提供線程安全的緩存操作;提供過期策略;提供回收策略;緩存監控。當緩存的數據超過最大值時,使用LRU算法替換。這一篇我們將要談到一個新的本地緩存框架:Caffeine Cache。它也是站在巨人的肩膀上-Guava Cache,借著他的思想優化了算法發展而來。
  • 高性能緩存Caffeine來了
    我最早使用的本地緩存框架就是Guava Cache,總感覺google產品確實是精品,性能好,方便易用。如今更好的本地緩存框架來了,它就是Caffeine。Spring5更是放棄了使用多年的Guava而採用了Caffeine。
  • 緩存 | 從本地緩存到分布式緩存
    本地緩存:指的是在應用中的緩存組件,其最大的優點是應用和cache是在同一個進程內部,請求緩存非常快速,沒有過多的網絡開銷等,在單應用不需要集群支持或者集群情況下各節點無需互相通知的場景下使用本地緩存較合適;同時,它的缺點也是因為緩存跟應用程式耦合,多個應用程式無法直接的共享緩存,各應用或集群的各節點都需要維護自己的單獨緩存,對內存是一種浪費。
  • 來自未來的緩存 Caffeine,帶你揭開它的神秘面紗
    1、相同點:兩個都是緩存的方式2、不同點:redis是將數據存儲到內存裡caffeine是將數據存儲在本地應用裡caffeine和redis相比,沒有了網絡IO上的消耗3、聯繫:一般將兩者結合起來,形成一二級緩存。
  • 它才是Java本地緩存之王
    提到本地緩存,大家都能想到Guava Cache,它的優點是封裝了get,put操作;提供線程安全的緩存操作;提供過期策略;提供回收策略;緩存監控。當緩存的數據超過最大值時,使用LRU算法替換。這一篇我們將要談到一個新的本地緩存框架:Caffeine Cache。它也是站在巨人的肩膀上-Guava Cache,借著它的思想優化了算法發展而來。
  • Caffeine 和 Redis 居然可以這麼搭,想不到吧!
    為什麼要用多級緩存?如果只使用redis來做緩存我們會有大量的請求到redis,但是每次請求的數據都是一樣的,假如這一部分數據就放在應用伺服器本地,那麼就省去了請求redis的網絡開銷,請求速度就會快很多。但是使用redis橫向擴展很方便。
  • caffeine緩存庫:能讓你忘記Redis的咖啡因
    Caffeine 是基於 JAVA 8 的高性能緩存庫。相對於Guava,Caffeine性能更為優秀,API也更為友好,這當然是我選擇他的主要原因。,而不使用其他主流緩存庫。=[duration]: 創建緩存或者最近一次更新緩存後經過固定的時間間隔,刷新緩存weakKeys: 打開key的弱引用weakValues:打開value的弱引用softValues:打開value的軟引用recordStats:開發統計功能
  • 從零開始手寫緩存之如何實現固定緩存大小
    我打趣道,程式設計師三高不是高性能、高並發、高可用嗎?你是哪三高?每一個追求性能的開發者,都對高性能孜孜不倦地追求著,而緩存是我們踏上這條高性能大道的必經之路。小到 cpu 設計,大到服務分布式緩存,我們每時每刻都在接觸緩存,今天我們就一起學習下緩存的發展之路,以及如何如何手寫一個可以指定大小的 cache。
  • 「性能提升」擴展Spring Cache 支持多級緩存
    client 認證[1]Caffeine 來自未來的本地內存緩存, L2 Redis 緩存。數據一致性:各層緩存之間的數據一致性問題,如應用層緩存和分布式緩存之前的數據一致性問題。先調用 caffeine 查詢是否存在指定的值 Object value = caffeineCache.getIfPresent(key); if (value != null) { log.debug("get cache from caffeine, the key is : {}", cacheKey); return value; } // 2.
  • 已關注「性能提升」擴展Spring Cache 支持多級緩存
    Caffeine 來自未來的本地內存緩存,性能比如常見的內存緩存實現性能高出不少詳細對比[2]。緩存。先調用 caffeine 查詢是否存在指定的值 Object value = caffeineCache.getIfPresent(key); if (value != null) { log.debug("get cache from caffeine, the key is : {}", cacheKey); return value; } // 2.
  • 床長人工智慧教程——Caffeine Cache實戰
    簡介是基於的高性能緩存庫。提供的內存緩存使用參考的。是基於設計經驗上改進的成果。並發測試官方性能比較可以清楚的看到效率明顯的高於其他緩存。可以通過建造者模式靈活的組合以下特性通過異步自動加載實體到緩存中基於大小的回收策略基於時間的回收策略自動刷新自動封裝虛引用自動封裝弱引用或軟引用實體過期或被刪除的通知寫入外部資源統計累計訪問緩存一填充策略提供了種加載策略手動加載,同步加載
  • 了解一下高性能緩存資料庫Redis,再去參加面試
    高性能,每秒可進行超過10萬次讀寫操作,是已知最快的Key-Value資料庫。Redis 在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(SortedSet)也使得我們在執行這些操作的時候變的非常簡單,Redis 只是正好提供了這兩種數據結構。由於是純內存操作,資料庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫。
  • 使用Caffeine和springboot的多級緩存配置
    #使用Caffeine和springboot的多級緩存配置因此,在許多應用程式中,包括普通的Spring和Spring Boot,您都可以@Cacheable在任何方法上使用它,並且其結果將被緩存,以便下次調用該方法時,將返回緩存的結果。但是,提供的緩存管理器僅允許您配置一個緩存規範。緩存規範包括到期時間,初始容量,最大大小等。
  • Win7 cmd命令刷新本地DNS緩存
    清除DNS緩存(我們輸入域名訪問網站的時候,系統會將網站ip存儲起來,再次訪問時會直接將其提取,加速網址的解析,而這個操作就是所謂的DNS緩存)後,訪問網站,會重新生成最新的DNS緩存,這時能夠防止期間DNS發生新的變化而本地卻沒有改變,造成訪問失敗!這篇文章是給大家帶來的Win7手動刷新DNS緩存方法教程。
  • 隨機IOPS全面超越,騰訊雲CSG存儲網關高性能緩存技術詳解
    CSG存儲網關是基於騰訊雲高性能、高可靠性的對象存儲系統COS對外提供iSCSI、NFS和CIFS/SMB訪問協議。作為一種混合雲方案, 幫助用戶不用修改本地應用就可以把數據上傳到COS。網關可以部署在用戶IDC也可以部署在騰訊雲上,產品形態如下所示。
  • 如何設計一個超牛逼的本地緩存,太香了
    ehcache來對比可能稍有差距;本文我們將來整理一下實現一個本地緩存都應該需要考慮哪些東西。;本地緩存如ehcache是支持持久化的,而guava是沒有持久化功能的;分布式緩存如redis是有持久化功能的,memcached是沒有持久化功能的;8.阻塞機制在看Mybatis源碼的時候,二級緩存提供了一個blocking標識,表示當在緩存中找不到元素時,它設置對緩存鍵的鎖定;
  • 廢土之王雙人本地聯機 按鍵衝突無反應解決辦法
    《廢土之王》是一款酣暢淋漓的像素射擊遊戲,支持雙人本地聯機遊戲,但大家在體驗的時候可能遇到了不少問題,比如常見的關閉手柄,鍵盤手柄都可以使用,但是進入遊戲後卻變成了鍵盤同時操作2個人,手柄無法使用。
  • 高並發系統三大利器之緩存,你都會了嗎?
    軟體要做到用戶體驗好,響應速度快,緩存就是必不可少的一個神器。緩存又分進程內緩存和分布式緩存兩種:分布式緩存如redis、memcached等,還有本地(進程內)緩存如ehcache、GuavaCache、Caffeine等。
  • 高並發系統三大利器之緩存
    軟體要做到用戶體驗好,響應速度快,緩存就是必不可少的一個神器。緩存又分進程內緩存和分布式緩存兩種:分布式緩存如redis、memcached等,還有本地(進程內)緩存如ehcache、GuavaCache、Caffeine等。
  • 高並發系統三大利器之——緩存
    軟體要做到用戶體驗好,響應速度快,緩存就是必不可少的一個神器。緩存又分進程內緩存和分布式緩存兩種:分布式緩存如redis、memcached等,還有本地(進程內)緩存如ehcache、GuavaCache、Caffeine等。