緩存之美——如何選擇合適的本地緩存?

来源:https://www.cnblogs.com/jingdongkeji/Undeclared/17958300
-Advertisement-
Play Games

1、簡介 小編最近在使用系統的時候,發現儘管應用已經使用了redis緩存提高查詢效率,但是仍然有進一步優化的空間,於是想到了比分散式緩存性能更好的本地緩存,因此對領域內常用的本地緩存進行了一番調研,有早期的Guava緩存、在Guava上進一步傳承的Caffine以及自稱在Java中使用最廣泛的EhC ...


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 支持 分散式系統中對數據一致性要求高

 

作者:京東保險 郭盼

來源:京東雲開發者社區 轉載請註明來源

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄一、Docker快速創建MySQL實例1.1 創建1.3 創建資料庫二、AutoMigrate介紹與使用2.1 AutoMigrate介紹2.2 AutoMigrate 基本使用三、模型定義3.1 模型定義3.2 快速增刪改查3.3 約定3.4 gorm.Model四、表模型主鍵、表名、列名的約 ...
  • 在如今快節奏的社會中,對於程式員來說,不僅需要持續學習和鞏固技術知識,還需要找到一家穩定的公司來發展自己的職業生涯。然而,面臨著內容質量、碎片化和付費等問題的技術博客網站並不少見。 針對這些問題,我們推薦給大家一家解決程式員需求的一站式服務網站——百戰百勝(https://www.51fire.xy ...
  • 0 前言 KubeSphere 基於 [Istio] 向用戶提供金絲雀發佈功能,即: 引入服務的新版本,並向其發送一小部分流量來進行測試 同時,舊版本負責處理其餘的流量 如果一切順利,就可逐漸增加向新版本發送的流量,同時逐步停用舊版本 如出現任何問題,可用 KubeSphere 更改流量比例來回滾至 ...
  • 目錄包和 Crate定義模塊來控製作用域與私有性在模塊中對相關代碼進行分組引用模塊項目的路徑使用 pub 關鍵字暴露路徑二進位和庫 crate 包的最佳實踐super 開始的相對路徑創建公有的結構體和枚舉使用 use 關鍵字將路徑引入作用域創建慣用的 use 路徑.使用 use 引入函數使用 use ...
  • 在 Java 中,泛型的逆變(contravariance)和協變(covariance)是涉及到泛型類型轉換時的兩個重要概念。 協變(Covariance) 協變指的是子類型對象可以賦值給父類型引用的情況。在泛型中,協變表示如果 B 是 A 的子類,那麼 List<B> 就是 List<A> 的子 ...
  • 一、ThreadPoolExecutor類講解 1、線程池狀態: 五種狀態: 線程池的shutdown() 方法,將線程池由 RUNNING(運行狀態)轉換為 SHUTDOWN狀態 線程池的shutdownNow()方法,將線程池由RUNNING 或 SHUTDOWN 狀態轉換為 STOP 狀態。 ...
  • k8s第一個重要設計思想:控制器模式。k8s里第一個控制器模式的完整實現:Deployment。它實現了k8s一大重要功能:Pod的“水平擴展/收縮”(horizontal scaling out/in)。該功能從PaaS時代開始就是一個平臺級項目必備編排能力。 若你更新了Deployment的Po ...
  • 最近需要讀取和修改華為路由器的配置,使用Java語言開發,通過SSH連接,輸入命令並讀取響應。 1.添加mwiede/jsch依賴 如果使用Maven,可以在pom.xml文件中添加以下依賴: <dependencies> <dependency> <groupId>com.github.mwied ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...