快取之美——如何選擇合適的本地快取?
1、簡介
小編最近在使用系統的時候,發現儘管應用已經使用了 redis 快取提高查詢效率,但是仍然有進一步最佳化的空間,於是想到了比分散式快取效能更好的本地快取,因此對領域內常用的本地快取進行了一番調研,有早期的 Guava 快取、在 Guava 上進一步傳承的 Caffine 以及自稱在 Java 中使用最廣泛的 EhCache,那麼我們該怎麼選擇適合自己應用的快取呢,小編下面會簡單介紹,並將以上快取進行一個對比,希望幫助大家選擇最適合自己系統的本地快取。
2、Guava 快取簡介
Guava cache 是 Google 開發的 Guava 工具包中一套完善的 JVM 本地快取框架,底層實現的資料結構類似於 ConcurrentHashMap,但是進行了更多的能力擴充,包括快取過期時間設定、快取容量設定、多種淘汰策略、快取監控等,下面簡單介紹下這些功能及其使用方式。
2.1、快取過期時間設定
Guava 的過期時間設定有基於建立時間和最後一次訪問時間兩種策略.
(1) 基於建立時間
透過對比快取記錄的插入時間來判斷,比如設定過期時間為 5 分鐘,不管中間有沒有訪問,到時過期。
public Cache<String, String> createCache() {
return CacheBuilder.newBuilder()
.expireAfterWrite(5L, TimeUnit.MINUTES)
.build();
}
(2) 基於過期時間
透過對比最近最後一次的訪問時間,比如設定 5 分鐘,每次訪問之後都會重新整理過期時間為 5 分鐘,只有持續 5 分鐘沒有被訪問到才會過期。
public Cache<String, String> createCache() {
return CacheBuilder.newBuilder()
.expireAfterAccess(5L, TimeUnit.MINUTES)
.build();
}
2.2、快取容量和淘汰策略設定
Guava cache 是記憶體型快取,有記憶體溢位風險,因此需要設定快取的最大儲存上限,透過快取的條數或每條快取的權重來判斷是否達到了設定閾值,當快取的資料量達到設定閾值之後,Guava cache 支援使用 FIFO 和 LRU 的策略對快取記錄採取淘汰的措施。
(1)限制快取記錄條數
public Cache<String, User> createCache() {
return CacheBuilder.newBuilder()
.maximumSize(100L)
.build();
}
(2)限制快取記錄權重
public Cache<String, User> createCache() {
return CacheBuilder.newBuilder()
.maximumWeight(100L)
.weigher((key, value) -> (int) Math.ceil(instrumentation.getObjectSize(value) / 1024L))
.build();
}
使用限制快取記錄權重時要先計算 weight 的 value 物件的位元組數,每 1kb 位元組作為一個權重,對比限制快取記錄,我們就能將快取的總佔用限制在 100kb 左右。
2.3 快取監控
快取記錄的載入和命中情況是評價快取處理能力的重要指標,Guava cache 提供了 stat 統計日誌對這兩個指標進行了統計,我們只需要在建立快取容器的時候加上 recordStats 就可以開啟統計。
public Cache<String, User> createCache() {
return CacheBuilder.newBuilder()
.recordStats()
.build();
}
2.4 Guava cache 的優劣勢和適用場景
優劣勢:Guava cache 透過記憶體處理資料,具有減少 IO 請求,讀寫效能快的優勢,但是受記憶體容量限制,只能處理少量資料的讀寫,還有可能對本機記憶體造成壓力,並且在分散式部署中,會存在不同機器節點資料不一致的情況,即快取漂移。
適用場景:讀多寫少,對資料一致性要求不高的場景。
3、Caffeine 簡介
Caffeine 同樣是 Google 開發的,是在 Guava cache 的基礎上改良而來的,底層設計思路、功能和使用方式與 Guava 非常類似,但是各方面的效能都要遠遠超過前者,可以看做是 Guava cache 的升級版,因此,之前使用過 Guava cache,也能夠很快的上手 Caffeine,下面是 Caffeine 和 Guava cache 的快取建立對比,基本可以無門檻過渡。
public Cache<String, String> createCache() {
return Caffeine.newBuilder()
.initialCapacity(1000)
.maximumSize(100L)
.expireAfterWrite(5L, TimeUnit.MINUTES)
.recordStats()
.build();
}
public Cache<String, String> createCache() {
return CacheBuilder.newBuilder()
.initialCapacity(1000)
.maximumSize(100L)
.expireAfterWrite(5L, TimeUnit.MINUTES)
.recordStats()
.build();
}
那麼 Caffeine 底層又做了哪些最佳化,才能讓其效能高於 Guava cache 呢?主要包含以下三點:
3.1、對比 Guava cache 的效能主要最佳化項
(1)非同步策略
Guava cache 在讀操作中可能會觸發淘汰資料的清理操作,雖然自身也做了一些最佳化來減少讀的時候的清理操作,但是一旦觸發,就會降低查詢效率,對快取效能產生影響。而在 Caffeine 支援非同步操作,採用非同步處理的策略,查詢請求在觸發淘汰資料的清理操作後,會將清理資料的任務新增到獨立的執行緒池中進行非同步操作,不會阻塞查詢請求,提高了查詢效能。
(2)ConcurrentHashMap 最佳化
Caffeine 底層都是透過 ConcurrentHashMap 來進行資料的儲存,因此隨著 Java8 中對 ConcurrentHashMap 的調整,陣列 + 連結串列的結構升級為陣列 + 連結串列 + 紅黑樹的結構以及分段鎖升級為 syschronized+CAS,降低了鎖的粒度,減少了鎖的競爭,這兩個最佳化顯著提高了 Caffeine 在讀多寫少場景下的查詢效能。
(3)新型淘汰演算法 W-TinyLFU
傳統的淘汰演算法,如 LRU、LFU、FIFO,在實際的快取場景中都存在一些弊端,如 FIFO 演算法,如果快取使用的頻率較高,那麼快取資料會一直處在進進出出的狀態,間接影響到快取命中率。LRU 演算法,在批次重新整理快取資料的場景下,可能會將其他快取資料淘汰掉,從而帶來快取擊穿的風險。LFU 演算法,需要儲存快取記錄的訪問次數,帶來記憶體空間的損耗。
因此,Caffeine 引入了 W-TinyLFU 演算法,由視窗快取、過濾器、主快取組成。快取資料剛進入時會停留在視窗快取中,這個部分只佔總快取的 1%,當被擠出視窗快取時,會在過濾器彙總和主快取中淘汰的資料進行比較,如果頻率更高,則進入主快取,否則就被淘汰,主快取被分為淘汰段和保護段,兩段都是 LRU 演算法,第一次被訪問的元素會進入淘汰段,第二次被訪問會進入保護段,保護段中被淘汰的元素會進入淘汰段,這種演算法實現了高命中率和低記憶體佔用。更詳細的解釋可以參考論文:https://arxiv.org/pdf/1512.00727.pdf
3.2、Caffeine 的優劣勢和適用場景
優勢:對比 Guava cache 有更高的快取效能,劣勢:仍然存在快取漂移的問題;JDK 版本過低無法使用
適用場景:1、適用場景:讀多寫少,對資料一致性要求不高的場景;2、純記憶體快取,JDK8 及更高版本中,追求比 Guava cache 更高的效能。
4、Ehcache 簡介
Guava cache 和 Caffeine 都是 JVM 快取,會受到記憶體大小的制約,最新的 Ehcache 採用堆內快取 + 堆外快取 + 磁碟的方式,打破了這一制約。堆內快取就是被 JVM 管理的那一部分快取,而堆外快取,就是在記憶體中另外在開闢一塊不被 JVM 管理的部分。堆外快取這部分既可以享受記憶體的高速讀寫能力,而且又避免的 JVM 頻繁的 GC,缺點是需要自行清理資料。
下面是 Ehcache 快取的建立,指定了堆內、堆外快取和磁碟快取的大小。
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(20, MemoryUnit.MB)
.offheap(10, MemoryUnit.MB)
.disk(5, MemoryUnit.GB);
為了解決快取漂移的問題,Ehcache 支援透過叢集的方式,實現了分散式節點之間的資料互通。關於 Ehcache 的叢集策略,後續文章再詳細闡述。
5、不同本地快取對比
框架 | 命中率 | 速度 | 回收演算法 | 使用難度 | 叢集 | 適用場景 |
---|---|---|---|---|---|---|
Guava cache | 中 | 第三 | LRU、LFU、FIFO | 易 | 不支援 | 讀多寫少,允許少量快取偏移 |
Caffeine | 高 | 第一 | W-TinyLFU | 易 | 不支援 | 讀多寫少,允許少量快取偏移,能用 Caffeine 就別用 Guava cache |
Ehcache | 中 | 第二 | LRU、LFU、FIFO | 中 | 支援 | 分散式系統中對資料一致性要求高 |
作者:京東保險 郭盼
來源:京東雲開發者社群 轉載請註明來源
相關文章
- PHP快取之Opcode快取PHP快取
- 億級流量客戶端快取之Http快取與本地快取對比客戶端快取HTTP
- 如何選擇適合的美顏SDK?
- mybatis快取之一級快取(一)MyBatis快取
- mybatis快取之一級快取(二)MyBatis快取
- 如何選擇合適的美國伺服器?伺服器
- ASP.NET Core - 快取之分散式快取ASP.NET快取分散式
- ASP.NET Core - 快取之記憶體快取(下)ASP.NET快取記憶體
- ASP.NET Core - 快取之記憶體快取(上)ASP.NET快取記憶體
- Vue 全站快取之 keep-alive : 動態移除快取Vue快取Keep-Alive
- 詳解快取更新策略及如何選擇快取
- 聊聊本地快取和分散式快取快取分散式
- 如何選擇合適的 BI 工具?
- Flutter快取之mmkvFlutter快取
- lodash原始碼分析之快取方式的選擇原始碼快取
- 【ionic】storage本地快取快取
- 如何選擇合適的建站系統
- 如何在SPRING中同時管理本地快取和分散式快取? - techblogSpring快取分散式
- 探討下如何更好的使用快取 —— Redis快取的特殊用法以及與本地快取一起構建多級快取的實現快取Redis
- JAVA中使用最廣泛的本地快取?Ehcache的自信從何而來3 —— 本地快取變身分散式叢集快取,打破本地快取天花板Java快取分散式
- 基於ObjectMapper的本地快取ObjectAPP快取
- Redis 快取之三Redis快取
- 如何為DMAIC選擇合適的專案AI
- 伺服器如何選擇合適的配置伺服器
- 如何選擇合適自己的伺服器伺服器
- 如何選擇最好最適合你的MacBookMac
- 如何選擇合適的NoSQL資料庫SQL資料庫
- 最強本地快取Caffeine快取
- 對比Memcached和Redis,誰才是適合你的快取?Redis快取
- 系統架構設計:程式快取和快取服務,如何抉擇?架構快取
- 固定容量的本地快取設計快取
- 如何選擇適合RCD負載箱負載
- 如何選取合適的前端動效方案?前端
- 伺服器如何選擇合適的IO模型伺服器模型
- 團隊如何選擇合適的Git分支策略?Git
- 如何選擇適合大學使用的郵箱
- 如何選擇適合自己的虛擬主機
- 如何正確選擇適合的CRM系統?