本地緩存Caffeine詳解(含與Spring Cache集成)

目錄

一、介紹

二、Caffeine核心原理與架構設計

2.1 存儲引擎與數據結構

2.2 緩存淘汰策略

2.3 并發控制機制

?三、入門案例

3.1 引入依賴

3.2 測試接口

3.3 小結

四、Caffeine常用方法詳解

4.1?getIfPresent

4.2 get

4.3 put

4.4 putAll

4.5 invalidate

4.6 invalidateAll

五、構建一個更加全面的緩存

5.1、容量控制配置

(1)??initialCapacity(int)???

(2)??maximumSize(long)?? ?

(3)??maximumWeight(long)??

5.2、過期策略配置

(1)expireAfterAccess(long, TimeUnit)??

(2)??expireAfterWrite(long, TimeUnit)???

?(3)?expireAfter(Expiry)??

5.3 注意事項

六、整合Spring Cache

6.1 引入依賴

6.2 配置文件

6.3 使用

七、生產環境注意事項

八、實現Caffeine與Redis多級緩存完整策略(待完善)?


一、介紹

JDK內置的Map可作為緩存的一種實現方式,然而嚴格意義來講,其不能算作緩存的范疇。

原因如下:一是其存儲的數據不能主動過期;二是無任何緩存淘汰策略。

Caffeine是一個基于Java 8的高性能本地緩存庫,由Ben Manes開發,旨在提供快速、靈活的緩存解決方案。作為Guava Cache的現代替代品,Caffeine在性能、功能和靈活性方面都有顯著提升。

Caffeine作為Spring體系中內置的緩存之一,Spring Cache同樣提供調用接口支持。已成為Java生態中最受歡迎的本地緩存庫之一。

本文將全面介紹Caffeine的核心原理、使用方法和最佳實踐。

二、Caffeine核心原理與架構設計

2.1 存儲引擎與數據結構

Caffeine底層采用優化的ConcurrentHashMap作為主要存儲結構,并在此基礎上進行了多項創新:

  • ??分段存儲技術??:使用StripedBuffer實現無鎖化并發控制,將競爭分散到多個獨立緩沖區,顯著提升并發吞吐量。
  • ??頻率統計機制??:采用Count-Min Sketch算法記錄訪問頻率,以93.75%的準確率僅使用少量內存空間。
  • ??時間輪管理??:通過TimerWheel數據結構高效管理過期條目,實現納秒級精度的過期控制。

2.2 緩存淘汰策略

Caffeine采用了創新的Window-TinyLFU算法,結合了LRU和LFU的優點:

  • ??三區設計??:窗口區(20%)、試用區(1%)和主區(79%),各區使用LRU雙端隊列管理
  • ??動態調整??:根據訪問模式自動調整各區比例,最高可實現98%的緩存命中率
  • ??頻率衰減??:通過周期性衰減歷史頻率,防止舊熱點數據長期占據緩存

相比Guava Cache的LRU算法,Window-TinyLFU能更準確地識別和保留真正的熱點數據,避免"一次性訪問"污染緩存。

2.3 并發控制機制

Caffeine的并發控制體系設計精妙:

  • ??寫緩沖機制??:使用RingBuffer和MpscChunkedArrayQueue實現多生產者-單消費者隊列
  • ??樂觀鎖優化??:通過ReadAndWriteCounterRef等自定義原子引用降低CAS開銷
  • ??StampedLock應用??:在關鍵路徑上使用Java 8的StampedLock替代傳統鎖,提升并發性能

?三、入門案例

3.1 引入依賴

以springboot 2.3.x為例,

<!--  caffeine    -->
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId>
</dependency>

3.2 測試接口

package com.example.demo;import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.UUID;@RestController
@RequestMapping("/api")
public class Controller {@GetMapping("writeCache")public String writeCache() {Cache<Object, Object> cache = Caffeine.newBuilder().build();cache.put("uuid", UUID.randomUUID());User user = new User("張三", "123456@qq.com", "abc123", 18);cache.put("user", user);return "寫入緩存成功";}@GetMapping("readCache")public String readCache() {Cache<Object, Object> cache = Caffeine.newBuilder().build();Object uuid = cache.getIfPresent("uuid");Object user = cache.getIfPresent("user");return "uuid: " + uuid + ", user: " + user;}}

?

問題:明明調用接口寫入了緩存,為什么我們查詢的時候還是沒有呢?

細心的你可能已經發現了,我們在每個接口都重新構造了一個新的Cache實例。這兩個Cache實例是完全獨立的,數據不會自動共享。

解決辦法

所以,聰明的你可能就想著把它提取出來,成功公共變量吧

@RestController
@RequestMapping("/api")
public class Controller {Cache<Object, Object> cache = Caffeine.newBuilder().build();@GetMapping("writeCache")public String writeCache() {cache.put("uuid", UUID.randomUUID());User user = new User("張三", "123456@qq.com", "abc123", 18);cache.put("user", user);return "寫入緩存成功";}@GetMapping("readCache")public String readCache() {Object uuid = cache.getIfPresent("uuid");Object user = cache.getIfPresent("user");return "uuid: " + uuid + ", user: " + user;}}

你看這不就有了!于是聰明的你,又想:“如果放在這個控制器類下面,那我其他類中要是想調用,是不是不太好?”

于是你又把它放在一個配置類下面,用于專門管理緩存。

package com.example.demo;import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class CacheConfig {@Beanpublic Cache<String, Object> buildCache() {return Caffeine.newBuilder().build();}
}
@RestController
@RequestMapping("/api")
public class Controller {@Resourceprivate Cache<String, Object> cache;@GetMapping("writeCache")public String writeCache() {cache.put("uuid", UUID.randomUUID());User user = new User("張三", "123456@qq.com", "abc123", 18);cache.put("user", user);return "寫入緩存成功";}@GetMapping("readCache")public String readCache() {Object uuid = cache.getIfPresent("uuid");Object user = cache.getIfPresent("user");return "uuid: " + uuid + ", user: " + user;}}

聰明的你,發現依然可以呀!真棒!

于是你又靈機一動,多定義幾個bean吧,一個設置有效期,一個永不過期。

@Configuration
public class CacheConfig {@Bean("noLimit")public Cache<String, Object> buildCache() {return Caffeine.newBuilder().build();}@Bean("limited")public Cache<String, Object> buildLimitedCache() {// 設置過期時間是30sreturn Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build();}
}
@RestController
@RequestMapping("/api")
public class Controller {@Resource(name = "limited")private Cache<String, Object> cache;@GetMapping("writeCache")public String writeCache() {cache.put("uuid", UUID.randomUUID());User user = new User("張三", "123456@qq.com", "abc123", 18);cache.put("user", user);return "寫入緩存成功";}@GetMapping("readCache")public String readCache() {Object uuid = cache.getIfPresent("uuid");Object user = cache.getIfPresent("user");return "uuid: " + uuid + ", user: " + user;}}

你發現30s后加入的緩存也沒有了。

3.3 小結

通過這個案例,你似乎也覺察到了,Caffeine的基本使用方法

  1. 導入依賴
  2. 構建公共緩存對象(expireAfterWrite方法可以設置寫入后多久過期)
  3. 使用?put()?方法添加緩存
  4. 使用 getIfPresent() 方法讀取緩存
  5. 一旦重啟項目,緩存就都消失了(基于本地內存)!

四、Caffeine常用方法詳解

4.1?getIfPresent

@Nullable V getIfPresent(@CompatibleWith("K") @NonNull Object var1);

前面已經演示過了,這里就不在舉例了。意思是如果存在則獲取,不存在就是null。

4.2 get

@Nullable V get(@NonNull K var1, @NonNull Function<? super K, ? extends V> var2);

@GetMapping("readCache")
public String readCache() {Object uuid = cache.getIfPresent("uuid");Object user = cache.get("user", item -> {// 緩存不存在時,執行加載邏輯return new User("李四", "456789@qq.com", "def456", 20);});return "uuid: " + uuid + ", user: " + user;
}

4.3 put

?void put(@NonNull K var1, @NonNull V var2);

?入門案例也演示過了,就是添加緩存。使用方法和普通的map類似,都是key,value的形式。

4.4 putAll

void putAll(@NonNull Map<? extends @NonNull K, ? extends @NonNull V> var1);

putAll 顧名思義,就是可以批量寫入緩存。首先定義一個map對象,把要加入的緩存往map里面塞,然后把map作為參數傳遞給這個方法即可。

4.5 invalidate

手動清除單個緩存

cache.invalidate("key1");

4.6 invalidateAll

手動批量清除多個key

// 批量清除多個key
cache.invalidateAll(Arrays.asList("key1", "key2"));

手動清除所有緩存

// 清除所有緩存
cache.invalidateAll();

💡注意:

這些方法會立即從緩存中移除指定的條目。

Caffeine除了手動清除外,也和Redis一樣,有自動清除策略。這些將在下一張集中講解。

五、構建一個更加全面的緩存

前面我們演示時,通過Caffeine.newBuilder().build();就建完了緩存對象,頂多給它設置了一個過期時間。

但是關于這個緩存對象本身,還有很多東西是可以設置的,下面我們就詳細說說,還有哪些設置。

Caffeine.newBuilder() 提供了豐富的配置選項,可以創建高性能、靈活的緩存實例。以下是主要的可配置內容:

5.1、容量控制配置

(1)??initialCapacity(int)???

設置初始緩存容量

示例:.initialCapacity(100)?表示初始能存儲100個緩存對象

(2)??maximumSize(long)?? ?

按條目數量限制緩存大小

示例:.maximumSize(1000)?表示最多緩存1000個條目

(3)??maximumWeight(long)??

按自定義權重總和限制緩存大小

需要配合weigher()使用

示例:.maximumWeight(10000).weigher((k,v) -> ((User)v).getSize())

注意:maximumSize和maximumWeight不能同時使用

當緩存條目數超過最大設定值時,Caffeine會根據Window TinyLFU算法自動清除"最不常用"的條目

5.2、過期策略配置

(1)expireAfterAccess(long, TimeUnit)??

設置最后訪問后過期時間

示例:.expireAfterAccess(5, TimeUnit.MINUTES)

(2)??expireAfterWrite(long, TimeUnit)???

設置創建/更新后過期時間

示例:.expireAfterWrite(10, TimeUnit.MINUTES)

?(3)?expireAfter(Expiry)??

自定義過期策略

可以基于創建、更新、讀取事件分別設置

.expireAfter(new Expiry<String, Object>() {public long expireAfterCreate(String key, Object value, long currentTime) {return TimeUnit.HOURS.toNanos(1); // 創建1小時后過期}public long expireAfterUpdate(String key, Object value, long currentTime, long currentDuration) {return currentDuration; // 保持原過期時間}public long expireAfterRead(String key, Object value, long currentTime, long currentDuration) {return currentDuration; // 保持原過期時間}
})

5.3 注意事項

Caffeine的清除操作通常是異步執行的,如果需要立即清理所有過期條目,可以調用:

cache.cleanUp();

這個方法會觸發一次完整的緩存清理,包括所有符合條件的過期條目。

六、整合Spring Cache

前面介紹時說了,Caffeine作為Spring體系中內置的緩存之一,Spring Cache同樣提供調用接口支持。所以接下來,我們詳細實現整合過程。

6.1 引入依賴

<!--  caffeine    -->
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId>
</dependency><!-- cache -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>

6.2 配置文件

@Configuration
public class CacheConfig {@Beanpublic CacheManager cacheManager() {CaffeineCacheManager cacheManager = new CaffeineCacheManager();cacheManager.setCaffeine(Caffeine.newBuilder().initialCapacity(100)          // 初始容量.maximumSize(500)             // 最大緩存條目數.expireAfterWrite(10, TimeUnit.MINUTES) // 寫入后10分鐘過期.expireAfterAccess(5, TimeUnit.MINUTES) // 訪問后5分鐘過期.weakKeys()                   // 使用弱引用鍵.recordStats());              // 記錄統計信息return cacheManager;}
}

6.3 使用

具體使用方法可以參考前面寫的這篇文章Spring Cache用法很簡單,但你知道這中間的坑嗎?-CSDN博客

springcache無非就是那幾個注解。這里淺淺舉例演示

@RestController
@RequestMapping("/api")
public class Controller {@GetMapping("test")@Cacheable(value = "demo")public User test() {System.out.println("-----------------------");return new User("張三", "123456@qq.com", "abc123", 18);}}


多次刷新,idea控制臺也僅僅打印了一次---------------------------

說明緩存生效了!

七、生產環境注意事項

提到緩存,那就是老生常談的:緩存穿透、緩存擊穿和緩存雪崩等問題。

緩存穿透防護??:

  • 對null值進行適當緩存(使用unless = "#result == null"
  • 考慮使用Bloom過濾器

??緩存雪崩防護??:

  • 為不同緩存設置不同的過期時間
  • 添加隨機抖動因子到過期時間

??緩存一致性??:

  • 重要數據建議配合數據庫事務
  • 考慮使用@CachePut更新策略

??內存管理??:

  • 合理設置maximumSize防止OOM
  • 對大對象考慮使用weakValues()softValues()

??分布式環境??:

  • 本地緩存需要配合消息總線實現多節點同步
  • 或考慮使用多級緩存(本地+Redis)

八、實現Caffeine與Redis多級緩存完整策略(待完善)?

在現代高并發系統中,多級緩存架構已成為提升系統性能的關鍵手段。Spring Cache通過抽象緩存接口,結合Caffeine(一級緩存)和Redis(二級緩存),可以構建高效的多級緩存解決方案。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/912072.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/912072.shtml
英文地址,請注明出處:http://en.pswp.cn/news/912072.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

論特定領域軟件架構

20250413-作 題目 特定領域軟件架構DSSA&#xff08;Domain Specific Software Architecture&#xff09;就是在一個特定應用領域中為一組應用提供組織結構參考的標準軟件體系結構。對DSSA 研究的角度、關心的問題不同導致了對DSSA 的不同定義。DSSA 的必備特征如下。 一…

iOS 遠程調試與離線排查實戰:構建非現場問題復現機制

iOS開發者都知道&#xff0c;調試最怕兩個字&#xff1a;“偶發”。用戶說App閃退了&#xff0c;你點了十遍也沒問題&#xff1b;測試說功能卡頓了&#xff0c;你抓日志時它又順滑如新。最麻煩的是&#xff0c;這種“現場問題”往往在你連接不到用戶設備時發生。 面對這種情況…

SpringBoot -- 整合 Swagger3

8.SpringBoot3 整合 Swagger3 由于目前主流的開發模式是前后端分離開發。所以前后端的交互需要通過一個 API&#xff08;開發接口&#xff09; 來規范。而這個接口的開發是由后端程序員編寫的。“網站式 API 文檔” 1.導入依賴 <!-- 引入swagger3(springdoc)的依賴--> …

A Machine Learning Approach for Non-blind Image Deconvolution論文閱讀

A Machine Learning Approach for Non-blind Image Deconvolution 1. 研究目標與實際意義2. 創新方法與模型設計2.1 核心思路2.2 正則化反演:理論與公式2.2.1 退化模型2.2.2 正則化目標函數2.2.3 傅里葉域閉式解2.3 MLP去偽影:架構與訓練2.3.1 MLP架構設計2.3.2 訓練流程2.3.…

微信小程序<rich-text>支持里面圖片點擊放大

使用<rich-text>渲染類似下面的html代碼&#xff1a; <div stylecolor: red>寵物友好<br/>xxx提供寵物友好服務&#xff0c;具體請見下圖<br/></div> <img srchttps://xxx.com/xxx1.png width100%/> <img srchttps://xxx.com/xxx2.png…

BVH 文件是一種用于記錄 3D 動畫數據的文件格式,常用于 3D 建模和動畫制作。以下是對這個 BVH 文件的逐行解讀

BVH 文件是一種用于記錄 3D 動畫數據的文件格式&#xff0c;常用于 3D 建模和動畫制作。以下是對這個 BVH 文件的逐行解讀&#xff1a; HIERARCHY ROOT Hips { OFFSET 0 0 0 CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation JOINT LeftUpLeg { OFFSE…

C語言開發:Onvif(一)

根據ONVIF官網 的介紹&#xff1a; ONVIF是一個開放的安防行業組織&#xff0c;致力于為安防行業提供和促進標準化開放接口&#xff0c;以實現IP網絡安防產品和服務的有效互操作性。 在具體實現上&#xff0c;ONVIF使用了Web Service的方式&#xff0c;設備通過WSDL定義的接口…

中科米堆三維掃描儀耳機3D掃描尺寸測量數字化建模

當下&#xff0c;耳機已從單純的音頻輸出設備進化為集娛樂、健康、辦公于一體的智能穿戴終端。相關數據顯示&#xff0c;2025年全球智能個人音頻設備出貨量突破4.55億臺&#xff0c;中國以22%的增速領跑全球&#xff0c;其中開放式耳機&#xff08;OWS&#xff09;出貨量占比達…

學習接口自動化框架pytest有哪些好處?

學習 pytest 作為接口自動化測試框架&#xff0c;具有以下顯著優勢&#xff0c;能大幅提升測試效率和質量&#xff1a; Pytest自動化測試教程&#xff0c;自動化必備之Pytest測試框架訓練營&#xff0c;只需一小時速成&#xff0c;學會直接上手實操&#xff01; 1. 代碼簡潔&…

LinuxBridge的作用與發展歷程:從基礎橋接到云原生網絡基石

LinuxBridge作為Linux網絡虛擬化的核心組件&#xff0c;已悄然支撐互聯網基礎設施20余年。它不僅是虛擬機網絡連接的橋梁&#xff0c;更是容器網絡、云原生架構的隱形支柱。 一、LinuxBridge的核心作用剖析 1. 二層網絡虛擬化的實現者 本質功能&#xff1a;模擬物理交換機行為…

探討 SYSCFG 時鐘對 ADC 采樣的影響

探討 SYSCFG 時鐘對 ADC 采樣的影響 在嵌入式系統開發中&#xff0c;對于 GD32F4 系列微控制器&#xff0c;理解 SYSCFG 時鐘與 ADC 采樣之間的關系至關重要。 以下是詳細分析&#xff1a; 一、SYSCFG 時鐘對 ADC 采樣過程的基礎性作用 1. 提供采樣時鐘信號 ADC 采樣需要時鐘…

Spring Boot 項目實訓 - 圖書信息網站

文章目錄 1. 實訓目的2. 系統設計3. 運行演示4. 實現步驟4.1 創建數據庫與表4.2 創建Spring Boot項目4.3 創建實體類4.4 創建映射器接口4.5 編寫應用配置文件4.6 測試映射器接口4.7 創建服務類4.8 創建控制器4.9 準備靜態資源4.10 創建模板頁面4.11 測試整個項目 5. 實訓總結 1…

GPU 性能可變性分析框架

大家讀完覺得有幫助記得關注和點贊&#xff01;&#xff01;&#xff01; 抽象。 分析來自 GPU 分析器的大規模性能日志通常需要數 TB 的內存和數小時的運行時間&#xff0c;即使是基本摘要也是如此。這些限制會阻止及時洞察&#xff0c;并阻礙將性能分析集成到自動化工作流程中…

Redis主從復制原理

一、Redis主從復制概述 Redis主從復制&#xff08;Master-Slave Replication&#xff09;是Redis實現高可用性和讀寫分離的基礎架構。通過主從復制&#xff0c;數據可以從一個Redis服務器&#xff08;主節點&#xff09;復制到一個或多個Redis服務器&#xff08;從節點&#x…

將ONNX模型轉換為(OPENMV可用的格式)TensorFlow Lite格式

將ONNX模型轉換為TensorFlow Lite格式 在深度學習模型部署過程中&#xff0c;我們常常需要將模型從一種格式轉換為另一種格式&#xff0c;以適應不同的硬件平臺和應用場景。本文將詳細介紹如何將ONNX格式的模型轉換為TensorFlow Lite格式&#xff0c;以便在移動設備或嵌入式設…

EXILIUM×亞矩云手機:重構Web3虛擬生存法則,開啟多端跨鏈元宇宙自由征途

在鏈游與元宇宙賽道競爭加劇的當下&#xff0c;EXILIUM憑借其去中心化開放世界、鏈上資產確權與玩家自治經濟系統&#xff0c;成為Web3原住民逃離“中心化牢籠”的首選之地。然而&#xff0c;其多鏈交互門檻高、跨設備身份管理復雜、鏈上安全風險頻發等問題&#xff0c;讓普通玩…

用3個字符表示2字節二進制數據

把1字節二進制數據&#xff0c;用可打印字符表示&#xff0c;可用十六進制。這樣一來&#xff0c;1字節變2字節&#xff0c;2倍。 2字節的二進制數據&#xff0c;可以用3個字符來表示&#xff0c;1.5倍。 2562略小于413&#xff0c;65536<68921 需要準備41個符號 包括&…

第 3 章:神經網絡如何學習

第 3 章&#xff1a;神經網絡如何學習 在第二章中&#xff0c;我們詳細了解了神經網絡的靜態結構&#xff1a;由神經元組成的層&#xff0c;以及連接它們的權重和偏置。現在&#xff0c;我們將進入整個教程最核心的部分&#xff1a;神經網絡是如何從數據中"學習"的&…

RocketMQ 消息長輪詢

文章目錄 問題所在&#xff1a;消費者如何高效地獲取消息&#xff1f;解決方案&#xff1a;長輪詢 (Long Polling - “等待與觀察”模式)長輪詢 vs. 短輪詢&#xff08;可視化對比&#xff09;為什么這個機制對 RocketMQ 這么好&#xff1f;關鍵的配置參數 讓我們用一個簡單易懂…

TensorFlow Serving學習筆記3: 組件調用關系

一、整體架構 TensorFlow Serving 采用模塊化設計&#xff0c;核心組件包括&#xff1a; Servables&#xff1a;可服務對象&#xff08;如模型、查找表&#xff09;Managers&#xff1a;管理 Servable 生命周期&#xff08;加載/卸載&#xff09;Loaders&#xff1a;負責 Ser…