本地緩存性能之王Caffeine
掃描二維碼
隨時(shí)隨地手機(jī)看文章
前言
隨著互聯(lián)網(wǎng)的高速發(fā)展,市面上也出現(xiàn)了越來越多的網(wǎng)站和app。我們判斷一個(gè)軟件是否好用,用戶體驗(yàn)就是一個(gè)重要的衡量標(biāo)準(zhǔn)。比如說我們經(jīng)常用的微信,打開一個(gè)頁(yè)面要十幾秒,發(fā)個(gè)語(yǔ)音要幾分鐘對(duì)方才能收到。相信這樣的軟件大家肯定是都不愿意用的。軟件要做到用戶體驗(yàn)好,響應(yīng)速度快,緩存就是必不可少的一個(gè)神器。緩存又分進(jìn)程內(nèi)緩存和分布式緩存兩種:分布式緩存如redis、memcached等,還有本地(進(jìn)程內(nèi))緩存如ehcache、GuavaCache、Caffeine等。說起Guava Cache,很多人都不會(huì)陌生,它是Google Guava工具包中的一個(gè)非常方便易用的本地化緩存實(shí)現(xiàn),基于LRU算法實(shí)現(xiàn),支持多種緩存過期策略。由于Guava的大量使用,Guava Cache也得到了大量的應(yīng)用。但是,Guava Cache的性能一定是最好的嗎?也許,曾經(jīng)它的性能是非常不錯(cuò)的。正所謂長(zhǎng)江后浪推前浪,前浪被拍在沙灘上。我們就來介紹一個(gè)比Guava Cache性能更高的緩存框架:Caffeine。
Tips: Spring5(SpringBoot2)開始用Caffeine取代guava.詳見官方信息SPR-13797
https://jira.spring.io/browse/SPR-13797
官方性能比較
以下測(cè)試都是基于jmh測(cè)試的,官網(wǎng)地址
測(cè)試為什么要基于jmh測(cè)試,可以參考知乎上R回答
在HotSpot VM上跑microbenchmark切記不要在main()里跑循環(huán)計(jì)時(shí)就完事。這是典型錯(cuò)誤。重要的事情重復(fù)三遍:請(qǐng)用JMH,請(qǐng)用JMH,請(qǐng)用JMH。除非非常了解HotSpot的實(shí)現(xiàn)細(xì)節(jié),在main里這樣跑循環(huán)計(jì)時(shí)得到的結(jié)果其實(shí)對(duì)一般程序員來說根本沒有任何意義,因?yàn)闊o(wú)法解釋。
8個(gè)線程讀,100%的讀操作
6個(gè)線程讀,2個(gè)線程寫,也就是75%的讀操作,25%的寫操作。
8個(gè)線程寫,100%的寫操作
對(duì)比結(jié)論
可以從數(shù)據(jù)看出來Caffeine的性能都比Guava要好。然后Caffeine的API的操作功能和Guava是基本保持一致的,并且 Caffeine為了兼容之前是Guava的用戶,做了一個(gè)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>
創(chuàng)建對(duì)象
1 Cache<String, Object> cache = Caffeine.newBuilder()
2 .initialCapacity(100)//初始大小
3 .maximumSize(200)//最大數(shù)量
4 .expireAfterWrite(3, TimeUnit.SECONDS)//過期時(shí)間
5 .build();
創(chuàng)建參數(shù)介紹
initialCapacity: 初始的緩存空間大小
maximumSize: 緩存的最大數(shù)量
maximumWeight: 緩存的最大權(quán)重
expireAfterAccess: 最后一次讀或?qū)懖僮骱蠼?jīng)過指定時(shí)間過期
expireAfterWrite: 最后一次寫操作后經(jīng)過指定時(shí)間過期
refreshAfterWrite: 創(chuàng)建緩存或者最近一次更新緩存后經(jīng)過指定時(shí)間間隔,刷新緩存
weakKeys: 打開key的弱引用
weakValues:打開value的弱引用
softValues:打開value的軟引用
recordStats:開發(fā)統(tǒng)計(jì)功能
注意:
expireAfterWrite和expireAfterAccess同時(shí)存在時(shí),以expireAfterWrite為準(zhǔn)。
maximumSize和maximumWeight不可以同時(shí)使用。
添加數(shù)據(jù)
Caffeine 為我們提供了手動(dòng)、同步和異步這幾種填充策略。
下面我們來演示下手動(dòng)填充策略吧,其他幾種如果大家感興趣的可以去官網(wǎng)了解下
1 Cache<String, String> cache = Caffeine.newBuilder()
2 .build();
3 cache.put("java金融", "java金融");
4 System.out.println(cache.getIfPresent("java金融"));
自動(dòng)添加(自定義添加函數(shù))
1 public static void main(String[] args) {
2 Cache<String, String> cache = Caffeine.newBuilder()
3 .build();
4 // 1.如果緩存中能查到,則直接返回
5 // 2.如果查不到,則從我們自定義的getValue方法獲取數(shù)據(jù),并加入到緩存中
6 String val = cache.get("java金融", k -> getValue(k));
7 System.out.println(val);
8 }
9 /**
10 * 緩存中找不到,則會(huì)進(jìn)入這個(gè)方法。一般是從數(shù)據(jù)庫(kù)獲取內(nèi)容
11 * @param k
12 * @return
13 */
14 private static String getValue(String k) {
15 return k + ":value";
16 }
過期策略
Caffeine 為我們提供了三種過期策略
,分別是基于大?。╯ize-based)、基于時(shí)間(time-based)、基于引用(reference-based)
基于大?。╯ize-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"));
運(yùn)行結(jié)果如下:淘汰了兩個(gè)只剩下一個(gè)。
1null
2null
3java金融3
基于時(shí)間(time-based)
Caffeine提供了三種定時(shí)驅(qū)逐策略:
expireAfterWrite(long, TimeUnit)
在最后一次寫入緩存后開始計(jì)時(shí),在指定的時(shí)間后過期。
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金融"));
運(yùn)行結(jié)果第三秒的時(shí)候取值為空。
1java金融
2java金融
3null
expireAfterAccess
在最后一次讀或者寫入后開始計(jì)時(shí),在指定的時(shí)間后過期。假如一直有請(qǐng)求訪問該key,那么這個(gè)緩存將一直不會(huì)過期。
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金融"));
運(yùn)行結(jié)果:讀和寫都沒有的情況下,3秒后才過期,然后就輸出了null。
1java金融
2java金融
3java金融
4null
expireAfter(Expiry)
在expireAfter中需要自己實(shí)現(xiàn)Expiry接口,這個(gè)接口支持expireAfterCreate,expireAfterUpdate,以及expireAfterRead了之后多久過期。注意這個(gè)是和expireAfterAccess、expireAfterAccess是互斥的。這里和expireAfterAccess、expireAfterAccess不同的是,需要你告訴緩存框架,他應(yīng)該在具體的某個(gè)時(shí)間過期,獲取具體的過期時(shí)間。
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 @Override
12 public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
13 return currentTime;
14 }
15
16 @Override
17 public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) {
18 return currentTime;
19 }
20 })
21 .build(k -> getValue(k));
刪除
單個(gè)刪除:Cache.invalidate(key)
批量刪除:Cache.invalidateAll(keys)
刪除所有緩存項(xiàng):Cache.invalidateAll
總結(jié)
本文只是對(duì)Caffeine的一個(gè)簡(jiǎn)單使用的介紹,它還有很多不錯(cuò)的東西,比如緩存監(jiān)控、事件監(jiān)聽、W-TinyLFU算法(高命中率、低內(nèi)存占用)感興趣的同學(xué)可以去官網(wǎng)查看。
參考
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://www.zhihu.com/question/58735131/answer/307771944
特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:
長(zhǎng)按訂閱更多精彩▼
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!