不羨鴛鴦不羨仙,一行代碼調半天。原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。java
我想把記憶緩存起來,等再次見到你,就可以很快認出你。git
可以說出這麼有哲理的話,得益於我對緩存的理解,以及對它的看重。沒有了緩存,個人人生就沒有了意義。程序員
緩存是很是重要的,工做中大部分工做能夠說是和緩存打交道。因爲使用普遍,因此針對緩存系統的任何優化,若是可以提升一丁點兒性能,就會讓人無比興奮。github
很長一段時間,我都在用Guava
的LoadingCache
。它和ConcurrentHashMap
是很是像的,但在其上封裝了一些好用的逐出策略和併發優化,就顯得好用的多。算法
今天主要說的是Caffeine
,中文名就是咖啡因,一種容易讓人精神亢奮的物質。它能夠說是Guava的重寫,可是效率卻很是的高,青出於藍而勝於藍。編程
下圖是Caffeine的一張性能測試圖。能夠看到它的性能,甩了GuavaCache老遠。這是爲何呢? localfile://media/15938442367524/15938448827543.jpg 緩存
首先要從它的做者開始提及。做者的github是( github.com/ben-manes ),曾經寫了ConcurrentLinkedHashMap
這個類,而這個類又是GuavaCache的基礎。
Ben Manes
一拍腦殼,決定更上層樓。
後浪Caffeine一來,GuavaCache就已經OUT了。微信
Caffeine支持異步加載方式,直接返回CompletableFutures
,相對於GuavaCache的同步方式,它不用阻塞等待數據的載入。另外,它的編程模型是友好的,省去了不少重複的工做。架構
GuavaCache是基於LRU的,而Caffeine是基於LRU和LFU的,結合了二者的優勢。對這兩個算法不太清楚的同窗,能夠參考xjjdog以前的文章: 《3種堆內緩存算法,贈源碼和設計思路》併發
二者合體以後,變成了新的W-TinyLFU
算法,它的命中率很是高,內存佔用更加的小,這是主要緣由所在。
Caffeine另一個比較快的緣由,就是不少操做都使用了異步,把這些事件提交到隊列裏。隊列使用的RingBuffer
,看到這個名詞,我不自覺的想到了lmax
的Disruptor
,它已經成了無鎖高併發的代名詞。
咱們決定拿線上的數據進行驗證一下。事實上,大部分比較重要的Cache,我都已經使用Caffeine替換了,完成了騷氣的升級。
因爲它們的API
長得很是像,這個過程是無痛的,連麻藥都不須要打。
其中有個業務,有一個大的堆內緩存,緩存了用戶數據。裏面包含用戶名、性別、地址、積分等屬性,造成了一個JSON對象,但大小不超過1KB。經過灰度,根據不一樣的策略,咱們測試了它的實際命中率。
策略1
1w
用戶命中率:
策略2
命中率(依然是高一籌):
策略3
此時的命中率:
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")
複製代碼
技術框架這麼多,什麼時候是盡頭。
做者簡介:小姐姐味道 (xjjdog),一個不容許程序員走彎路的公衆號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高併發世界,給你不同的味道。個人我的微信xjjdog0,歡迎添加好友,進一步交流。 交流。