新一代快取Caffeine,速度確實比Guava的Cache快
我想把記憶快取起來,等再次見到你,就能夠很快認出你。
能夠說出這麼有哲理的話,得益於我對快取的理解,以及對它的看重。沒有了快取,我的人生就沒有了意義。
快取是非常重要的,工作中大部分工作可以說是和快取打交道。由於使用廣泛,所以針對快取系統的任何優化,如果能夠提高一丁點兒效能,就會讓人無比興奮。
很長一段時間,我都在用Guava
的LoadingCache
。它和ConcurrentHashMap
是非常像的,但在其上封裝了一些好用的逐出策略和併發優化,就顯得好用的多。
今天主要說的是Caffeine
,中文名就是咖啡因,一種容易讓人精神亢奮的物質。它可以說是Guava的重寫,但是效率卻非常的高,青出於藍而勝於藍。
下圖是Caffeine的一張效能測試圖。可以看到它的效能,甩了GuavaCache老遠。這是為什麼呢? localfile://media/15938442367524/15938448827543.jpg
首先要從它的作者開始說起。作者的github是(github.com/ben-manes),曾經寫了ConcurrentLinkedHashMap
這個類,而這個類又是GuavaCache的基礎。Ben Manes
一拍腦袋,決定更上層樓。
為什麼說Caffeine好?
後浪Caffeine一來,GuavaCache就已經OUT了。
Caffeine支援非同步載入方式,直接返回CompletableFutures
GuavaCache是基於LRU的,而Caffeine是基於LRU和LFU的,結合了兩者的優點。對這兩個演算法不太清楚的同學,可以參考xjjdog之前的文章:《3種堆內快取演算法,贈原始碼和設計思路》
兩者合體之後,變成了新的W-TinyLFU
演算法,它的命中率非常高,記憶體佔用更加的小,這是主要原因所在。
Caffeine另外一個比較快的原因,就是很多操作都使用了非同步,把這些事件提交到佇列裡。佇列使用的RingBuffer
,看到這個名詞,我不自覺的想到了lmax
的Disruptor
測試命中率
我們決定拿線上的資料進行驗證一下。事實上,大部分比較重要的Cache,我都已經使用Caffeine替換了,完成了騷氣的升級。
由於它們的API
長得非常像,這個過程是無痛的,連麻藥都不需要打。
其中有個業務,有一個大的堆內快取,快取了使用者資料。裡面包含使用者名稱、性別、地址、積分等屬性,形成了一個JSON物件,但大小不超過1KB。通過灰度,根據不同的策略,我們測試了它的實際命中率。
策略1
- 最大快取
1w
使用者 - 資料進入快取後,5分鐘失效(需要重新讀取)
命中率:
- Caffeine 29.22 %
- Guava 21.95%
策略2
- 加大快取資料量到6w使用者
- 資料進入快取後,20分鐘失效,這個和Session有的一拼了
命中率(依然是高一籌):
- Caffeine 56.04 %
- Guava 50.01%
策略3
- 直接加大快取到15w使用者
- 資料進入快取後,30分鐘失效
此時的命中率:
- Caffeine 71.10 %
- Guava 62.76%
Caffeine的命中率一直是領先的。命中率高,效率自然也就高。調整到50%以上,我們的快取作用就很大了。
非同步載入
再放上官方的兩張測試圖:
(1) Read (75%) / Write (25%) localfile://media/15938442367524/15938463845793.jpg
(2) Write (100%) localfile://media/15938442367524/15938464259214.jpg
(3) Read (100%) localfile://media/15938442367524/15938464366776.jpg
我們一直在提Caffeine的非同步載入。那程式碼到底長什麼樣子呢?非同步載入快取使用了響應式程式設計模型,二手遊戲買賣平臺地圖返回的是CompletableFuture
物件。說實話,程式碼長得和Guava很像。
public static void main(String[] args) {
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
.maximumSize(1000)
.buildAsync(key -> slowMethod(key));
CompletableFuture<String> g = loadingCache.get("test");
String value = g.get();
}
static String slowMethod(String key) throws Exception {
Thread.sleep(1000);
return key + ".result";
}
複製程式碼
我記得前段時間翻Spring的原始碼時,也看到過它。 localfile://media/15938442367524/15938479597151.jpg
在SpringBoot裡,通過提供一個CacheManager
的Bean,即可與Springboot-cache
進行整合,可以說是很方便了。
關鍵程式碼。
//bean生成
@Bean("caffeineCacheManager")
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000));
return cacheManager;
}
//使用注入
@CacheConfig(cacheNames = "caffeineCacheManager")
//資訊快取
@Cacheable(key = "#id")
複製程式碼
技術框架這麼多,何時是盡頭。