新一代緩存Caffeine,速度確實比Guava的Cache快

不羨鴛鴦不羨仙,一行代碼調半天。原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。java

我想把記憶緩存起來,等再次見到你,就可以很快認出你。git

可以說出這麼有哲理的話,得益於我對緩存的理解,以及對它的看重。沒有了緩存,個人人生就沒有了意義。程序員

緩存是很是重要的,工做中大部分工做能夠說是和緩存打交道。因爲使用普遍,因此針對緩存系統的任何優化,若是可以提升一丁點兒性能,就會讓人無比興奮。github

很長一段時間,我都在用GuavaLoadingCache。它和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的同步方式,它不用阻塞等待數據的載入。另外,它的編程模型是友好的,省去了不少重複的工做。架構

GuavaCache是基於LRU的,而Caffeine是基於LRU和LFU的,結合了二者的優勢。對這兩個算法不太清楚的同窗,能夠參考xjjdog以前的文章: 《3種堆內緩存算法,贈源碼和設計思路》併發

二者合體以後,變成了新的W-TinyLFU算法,它的命中率很是高,內存佔用更加的小,這是主要緣由所在。

Caffeine另一個比較快的緣由,就是不少操做都使用了異步,把這些事件提交到隊列裏。隊列使用的RingBuffer,看到這個名詞,我不自覺的想到了lmaxDisruptor,它已經成了無鎖高併發的代名詞。

測試命中率

咱們決定拿線上的數據進行驗證一下。事實上,大部分比較重要的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")
複製代碼

技術框架這麼多,什麼時候是盡頭。

做者簡介:小姐姐味道 (xjjdog),一個不容許程序員走彎路的公衆號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高併發世界,給你不同的味道。個人我的微信xjjdog0,歡迎添加好友,​進一步交流。​ 交流。​

相關文章
相關標籤/搜索