您當前的位置:首頁 > 體育

快取之王Caffeine Cache,效能比Guava更強

作者:由 玩java的草雞 發表于 體育時間:2021-01-18

一、前言

在專案開發中,為提升系統性能,減少 IO 開銷,本地快取是必不可少的。最常見的本地快取是 Guava 和 Caffeine,本篇文章將為大家介紹 Caffeine。

Caffeine 是基於 Google Guava Cache 設計經驗改進的結果,相較於 Guava 在效能和命中率上更具有效率,你可以認為其是 Guava Plus。

毋庸置疑的,你應該儘快將你的本地快取從 Guava 遷移至 Caffeine,本文將重點和 Guava 對比二者效能佔據,給出本地快取的最佳實踐,以及遷移策略。

二、PK Guava

2。1

從功能上看,Guava 已經比較完善了,滿足了絕大部分本地快取的需求。Caffine 除了提供 Guava 已有的功能外,同時還加入了一些擴充套件功能。

2。2 效能

Guava 中其讀寫操作夾雜著過期時間的處理,也就是你在一次 put 操作中有可能會做淘汰操作,所以其讀寫效能會受到一定影響。

Caffeine 在讀寫操作方面完爆 Guava,主要是因為 Caffeine 對這些事件的操作是非同步的,將事件提交至佇列(使用 Disruptor RingBuffer),然後會透過預設的 ForkJoinPool。commonPool(),或自己配置的執行緒池,進行取佇列操作,然後再進行後續的淘汰、過期操作。

以下效能對比來自 Caffeine 官方提供資料:

(1)在此基準測試中,從配置了最大大小的快取中,8 個執行緒併發讀:

快取之王Caffeine Cache,效能比Guava更強

(2)在此基準測試中,從配置了最大大小的快取中,6個執行緒併發讀、2個執行緒併發寫:

快取之王Caffeine Cache,效能比Guava更強

(3)在此基準測試中,從配置了最大大小的快取中,8 個執行緒併發寫:

快取之王Caffeine Cache,效能比Guava更強

2。3 命中率

快取的淘汰策略是為了預測哪些資料在短期內最可能被再次用到,從而提升快取的命中率。Guava 使用 S-LRU 分段的最近最少未使用演算法,Caffeine 採用了一種結合 LRU、LFU 優點的演算法:W-TinyLFU,其特點是:高命中率、低記憶體佔用。

2。3。1 LRU

Least Recently Used:如果資料最近被訪問過,將來被訪問的機率也更高。每次訪問就把這個元素放到佇列的頭部,佇列滿了就淘汰佇列尾部的資料,即淘汰最長時間沒有被訪問的。

需要維護每個資料項的訪問頻率資訊,每次訪問都需要更新,這個開銷是非常大的。

其缺點是,如果某一時刻大量資料到來,很容易將熱點資料擠出快取,留下來的很可能是隻訪問一次,今後不會再訪問的或頻率極低的資料。比如外賣中午時候訪問量突增、微博爆出某明星糗事就是一個突發性熱點事件。當事件結束後,可能沒有啥訪問量了,但是由於其極高的訪問頻率,導致其在未來很長一段時間內都不會被淘汰掉。

2。3。2 LFU

Least Frequently Used:如果資料最近被訪問過,那麼將來被訪問的機率也更高。也就是淘汰一定時間內被訪問次數最少的資料(時間區域性性原理)。

需要用 Queue 來儲存訪問記錄,可以用 LinkedHashMap 來簡單實現一個基於 LRU 演算法的快取。

其優點是,避免了 LRU 的缺點,因為根據頻率淘汰,不會出現大量進來的擠壓掉 老的,如果在資料的訪問的模式不隨時間變化時候,LFU 能夠提供絕佳的命中率。

其缺點是,偶發性的、週期性的批次操作會導致LRU命中率急劇下降,快取汙染情況比較嚴重。

2。3。3 TinyLFU

TinyLFU 顧名思義,輕量級LFU,相比於 LFU 演算法用更小的記憶體空間來記錄訪問頻率。

TinyLFU 維護了近期訪問記錄的頻率資訊,不同於傳統的 LFU 維護整個生命週期的訪問記錄,所以他可以很好地應對突發性的熱點事件(超過一定時間,這些記錄不再被維護)。這些訪問記錄會作為一個過濾器,當新加入的記錄(New Item)訪問頻率高於將被淘汰的快取記錄(Cache Victim)時才會被替換。流程如下:

快取之王Caffeine Cache,效能比Guava更強

tiny-lfu-arch

儘管維護的是近期的訪問記錄,但仍然是非常昂貴的,TinyLFU 透過 Count-Min Sketch 演算法來記錄頻率資訊,它佔用空間小且誤報率低,關於 Count-Min Sketch 演算法可以參考論文:pproximating Data with the Count-Min Data Structure

2。3。4 W-TinyLFU

W-TinyLFU 是 Caffeine 提出的一種全新演算法,它可以解決頻率統計不準確以及訪問頻率衰減的問題。這個方法讓我們從空間、效率、以及適配舉證的長寬引起的雜湊碰撞的錯誤率上做均衡。

下圖是一個運行了 ERP 應用的資料庫服務中各種演算法的命中率,實驗資料來源於 ARC 演算法作者,更多場景的效能測試參見官網:

快取之王Caffeine Cache,效能比Guava更強

database

W-TinyLFU 演算法是對 TinyLFU演算法的最佳化,能夠很好地解決一些稀疏的突發訪問元素。在一些數目很少但突發訪問量很大的場景下,TinyLFU將無法儲存這類元素,因為它們無法在短時間內積累到足夠高的頻率,從而被過濾器過濾掉。W-TinyLFU 將新記錄暫時放入 Window Cache 裡面,只有透過 TinLFU 考察才能進入 Main Cache。大致流程如下圖:

快取之王Caffeine Cache,效能比Guava更強

W-TinyLFU

三、最佳實踐

3。1 實踐1

配置方式

:設定 maxSize、refreshAfterWrite,不設定 expireAfterWrite

存在問題

:get 快取間隔超過 refreshAfterWrite 後,觸發快取非同步重新整理,此時會獲取快取中的舊值

適用場景

快取資料量大,限制快取佔用的記憶體容量* 快取值會變,需要重新整理快取* 可以接受任何時間快取中存在舊資料

快取之王Caffeine Cache,效能比Guava更強

設定 maxSize、refreshAfterWrite,不設定 expireAfterWrite

3。2 實踐2

配置方式

:設定 maxSize、expireAfterWrite,不設定 refreshAfterWrite

存在問題

:get 快取間隔超過 expireAfterWrite 後,針對該 key,獲取到鎖的執行緒會同步執行 load,其他未獲得鎖的執行緒會阻塞等待,獲取鎖執行緒執行延時過長會導致其他執行緒阻塞時間過長

適用場景

快取資料量大,限制快取佔用的記憶體容量* 快取值會變,需要重新整理快取* 不可以接受快取中存在舊資料* 同步載入資料延遲小(使用 redis 等)

快取之王Caffeine Cache,效能比Guava更強

設定 maxSize、expireAfterWrite,不設定refreshAfterWrite

3。3 實踐3

配置方式

:設定 maxSize,不設定 refreshAfterWrite、expireAfterWrite,定時任務非同步重新整理資料

存在問題

:需要手動定時任務非同步重新整理快取

適用場景

快取資料量大,限制快取佔用的記憶體容量* 快取值會變,需要重新整理快取* 不可以接受快取中存在舊資料* 同步載入資料延遲可能會很大

快取之王Caffeine Cache,效能比Guava更強

g

設定 maxSize,不設定 refreshAfterWrite、expireAfterWrite,定時任務非同步重新整理資料

3。4 實踐4

配置方式

:設定 maxSize、refreshAfterWrite、expireAfterWrite,refreshAfterWrite < expireAfterWrite

存在問題

get 快取間隔在 refreshAfterWrite 和 expireAfterWrite 之間,觸發快取非同步重新整理,此時會獲取快取中的舊值* get 快取間隔大於 expireAfterWrite,針對該 key,獲取到鎖的執行緒會同步執行 load,其他未獲得鎖的執行緒會阻塞等待,獲取鎖執行緒執行延時過長會導致其他執行緒阻塞時間過長

適用場景

快取資料量大,限制快取佔用的記憶體容量* 快取值會變,需要重新整理快取* 可以接受有限時間快取中存在舊資料* 同步載入資料延遲小(使用 redis 等)

快取之王Caffeine Cache,效能比Guava更強

設定 maxSize、refreshAfterWrite、expireAfterWrite

四、遷移指南

4.1 切換至 Caffeine

在 pom 檔案中引入 Caffeine 依賴:

com。github。ben-manes。caffeine

caffeine

Caffeine 相容 Guava API,從 Guava 切換到 Caffeine,僅需要把 CacheBuilder。newBuilder()改成 Caffeine。newBuilder() 即可。

4.2 Get Exception

需要注意的是,在使用 Guava 的 get()方法時,當快取的 load()方法返回 null 時,會丟擲 ExecutionException。切換到 Caffeine 後,get()方法不會丟擲異常,但允許返回為 null。

Guava 還提供了一個getUnchecked()方法,它不需要我們顯示的去捕捉異常,但是一旦 load()方法返回 null時,就會丟擲 UncheckedExecutionException。切換到 Caffeine 後,不再提供 getUnchecked()方法,因此需要做好判空處

標簽: 快取  Guava  caffeine  訪問  執行緒