SpringBoot應(yīng)用中的多層緩存實(shí)踐
在本文中,我們將深入探討 Spring Boot 應(yīng)用中多層緩存的實(shí)現(xiàn)思路。具體而言,我們會采用本地一級緩存(L1) 與遠(yuǎn)程分布式二級緩存(L2) 的組合方案:其中一級緩存使用較短的過期時間,二級緩存則配置較長的過期時間。
多層緩存的核心邏輯是優(yōu)先查詢本地緩存,若本地緩存未命中,則再查詢二級緩存。其核心目標(biāo)是通過減少與遠(yuǎn)程服務(wù)的往返通信次數(shù),顯著提升應(yīng)用性能。
技術(shù)選型說明
- 一級緩存(L1) :采用 Caffeine 緩存庫實(shí)現(xiàn)。Caffeine 是當(dāng)前性能最優(yōu)的本地緩存方案之一,基于 W-TinyLFU 淘汰算法(高命中率),支持高效的內(nèi)存管理、靈活的過期策略(寫入后過期、訪問后過期等),且對 Java 8+ 特性適配良好。
- 二級緩存(L2) :采用 Redis 實(shí)現(xiàn)。Redis 作為主流的分布式內(nèi)存數(shù)據(jù)存儲,具備高可用、高并發(fā)支持能力,適合作為跨服務(wù)共享的二級緩存。
需要說明的是,本文的實(shí)現(xiàn)方案具備通用性——只需輕微調(diào)整,即可適配 Ehcache、Hazelcast 等其他緩存組件。
一、Caffeine 依賴引入
要在 Spring Boot 項(xiàng)目中使用 Caffeine,需先引入對應(yīng)的依賴。推薦通過 Spring Boot 官方的依賴管理(spring-boot-dependencies)控制版本,避免版本沖突;若未使用 Spring Boot 父工程,則需手動指定 Caffeine 版本。
Maven 項(xiàng)目依賴
在 pom.xml 中添加以下依賴:
<!-- 方式1:直接引入 Caffeine 核心依賴(推薦,版本由 Spring Boot 父工程管理) -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- 方式2:若需使用 Spring 對 Caffeine 的緩存適配(如 CaffeineCacheManager),可引入 spring-boot-starter-cache -->
<!-- 注:spring-boot-starter-cache 已間接包含 Caffeine 依賴(需確保項(xiàng)目已啟用緩存抽象) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
Caffeine 優(yōu)勢
選擇 Caffeine 作為一級緩存,核心原因在于其卓越的性能與靈活性:
- 高命中率:采用 W-TinyLFU(Window Tiny Least Frequently Used)淘汰算法,在高并發(fā)場景下比傳統(tǒng) LRU 算法命中率提升 10%-20%。
- 低延遲:底層基于 Java 并發(fā)容器(如
ConcurrentHashMap),讀寫操作均為低延遲設(shè)計(jì),支持每秒數(shù)百萬次緩存訪問。 - 靈活的過期策略:支持
expireAfterWrite(寫入后過期)、expireAfterAccess(訪問后過期)、expireAfter(自定義過期邏輯),滿足不同業(yè)務(wù)場景。 - 內(nèi)存安全:支持配置最大緩存容量(
maximumSize),當(dāng)緩存達(dá)到閾值時自動淘汰舊數(shù)據(jù),避免內(nèi)存溢出。
二、簡單卻粗糙的雙層緩存實(shí)現(xiàn)方案
在 Spring Boot 中,我們通常使用 @Cacheable 注解實(shí)現(xiàn)方法結(jié)果緩存。若要實(shí)現(xiàn)雙層緩存,最直接的思路是:手動檢查一級緩存(Caffeine),未命中時再查詢二級緩存(Redis)。
首先看二級緩存服務(wù)的實(shí)現(xiàn)(基于 @Cacheable 注解):
public class L2CacheService {
@Cacheable(value = "myCache", key = "#id")
public String getFromCache(String id) {
// 該方法的返回結(jié)果會被自動緩存(默認(rèn)對接二級緩存)
return "Hello " + id;
}
}
在此基礎(chǔ)上,封裝一級緩存邏輯,形成雙層緩存調(diào)用:
public class L1CacheService {
@Autowired
private L2CacheService l2CacheService;
// 初始化 Caffeine 本地緩存:最大緩存數(shù) 100,寫入后 10 分鐘過期
private Cache<String, String> caffeineCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public String getFromCache(String id) {
// 1. 優(yōu)先查詢一級緩存
String result = caffeineCache.getIfPresent(id);
if (result == null) {
// 2. 一級緩存未命中,查詢二級緩存
result = l2CacheService.getFromCache(id);
// 3. 若二級緩存命中,同步到一級緩存
if (result != null) {
caffeineCache.put(id, result);
}
}
return result;
}
}
顯然,這種方案并不理想:代碼冗余度高,且需為每個緩存場景單獨(dú)創(chuàng)建服務(wù)類,極易因手動維護(hù)緩存邏輯導(dǎo)致錯誤(如緩存同步遺漏、過期時間不一致等)。我們有更優(yōu)的實(shí)現(xiàn)方式。
三、基于 Spring 緩存抽象的優(yōu)化方案
Spring 提供的 CompositeCacheManager(組合緩存管理器)可將多個緩存組件整合,大幅簡化雙層緩存的配置邏輯。以下是具體實(shí)現(xiàn):
1. 緩存配置類
@Configuration
@EnableCaching // 啟用 Spring 緩存抽象
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
// 1. 配置一級緩存(Caffeine)
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager("myCache");
caffeineCacheManager.setCaffeine(caffeineCacheBuilder());
caffeineCacheManager.setAllowNullValues(false); // 不允許緩存 null 值
// 2. 配置二級緩存(Redis)
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate());
redisCacheManager.setUsePrefix(true); // 啟用緩存鍵前綴,避免鍵沖突
// 3. 組合緩存管理器:優(yōu)先使用 Caffeine 緩存,未命中則查詢 Redis
CompositeCacheManager compositeCacheManager = new CompositeCacheManager(caffeineCacheManager, redisCacheManager);
compositeCacheManager.setFallbackToNoOpCache(true); // 當(dāng)緩存不存在時,啟用無操作緩存降級(避免報(bào)錯)
return compositeCacheManager;
}
// 配置 Caffeine 緩存策略
private Caffeine<Object, Object> caffeineCacheBuilder() {
return Caffeine.newBuilder()
.maximumSize(100) // 最大緩存條目數(shù)
.expireAfterWrite(10, TimeUnit.MINUTES); // 寫入后 10 分鐘過期
}
// 配置 Redis 模板(序列化方式)
@Bean
public RedisTemplate<String, String> redisTemplate() {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
// 配置鍵/值的序列化器(避免 Redis 中存儲亂碼)
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
// 配置 Redis 連接工廠(默認(rèn)使用 Lettuce 客戶端)
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
}
2. 業(yè)務(wù)服務(wù)實(shí)現(xiàn)
只需在方法上添加 @Cacheable 注解,即可自動觸發(fā)雙層緩存邏輯:
@Cacheable(value = "myCache", key = "#id")
public String getFromCache(String id) {
return "Hello " + id;
}
方案優(yōu)勢與局限性
- 優(yōu)勢:基于 Spring 原生緩存抽象,無需手動維護(hù)緩存調(diào)用邏輯,代碼簡潔且容錯性強(qiáng)。
- 局限性:
CompositeCacheManager僅支持“一級緩存未命中則查詢二級緩存”,但不會將二級緩存的命中結(jié)果同步到一級緩存。若需實(shí)現(xiàn)緩存同步,仍需在業(yè)務(wù)方法中手動編寫邏輯:
public String getFromCache(String id) {
// 1. 查詢一級緩存
String result = caffeineCacheManager.getCache("myCache").get(id, String.class);
if (result == null) {
// 2. 一級緩存未命中,查詢二級緩存
result = redisCacheManager.getCache("myCache").get(id, String.class);
// 3. 同步二級緩存結(jié)果到一級緩存
if (result != null) {
caffeineCacheManager.getCache("myCache").put(id, result);
}
}
return result;
}
顯然,這種“配置+手動代碼”的混合模式仍不完美。接下來,我們將通過自定義緩存管理器,實(shí)現(xiàn)真正無縫的雙層緩存同步。
四、基于自定義 CacheManager 的完美方案
通過實(shí)現(xiàn) Spring 的 CacheManager 接口與 Cache 接口,我們可以完全掌控雙層緩存的讀寫、同步與過期邏輯,實(shí)現(xiàn)“一次注解,雙向同步”。
1. 自定義緩存管理器(CustomCacheManager)
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import java.util.Collection;
public class CustomCacheManager implements CacheManager {
// 依賴注入一級緩存管理器(Caffeine)與二級緩存管理器(Redis)
private final CacheManager caffeineCacheManager;
private final CacheManager redisCacheManager;
public CustomCacheManager(CacheManager caffeineCacheManager, CacheManager redisCacheManager) {
this.caffeineCacheManager = caffeineCacheManager;
this.redisCacheManager = redisCacheManager;
}
// 獲取緩存實(shí)例:返回自定義的雙層緩存實(shí)現(xiàn)
@Override
public Cache getCache(String name) {
Cache caffeineCache = caffeineCacheManager.getCache(name);
Cache redisCache = redisCacheManager.getCache(name);
return new CustomCache(caffeineCache, redisCache);
}
// 獲取所有緩存名稱(與一級緩存保持一致)
@Override
public Collection<String> getCacheNames() {
return caffeineCacheManager.getCacheNames();
}
}
2. 自定義緩存實(shí)現(xiàn)(CustomCache)
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import java.util.concurrent.Callable;
// 采用 record 類型簡化代碼(JDK 16+ 支持),也可使用 class 實(shí)現(xiàn)
public record CustomCache(Cache firstLevelCache, Cache secondLevelCache) implements Cache {
// 緩存名稱:與一級緩存保持一致
@Override
public String getName() {
return firstLevelCache.getName();
}
// 原生緩存實(shí)例:返回一級緩存的原生實(shí)例
@Override
public Object getNativeCache() {
return firstLevelCache.getNativeCache();
}
// 緩存查詢:優(yōu)先查一級,未命中則查二級,并同步到一級
@Override
public ValueWrapper get(Object key) {
// 1. 查詢一級緩存
ValueWrapper valueWrapper = firstLevelCache.get(key);
if (valueWrapper == null) {
// 2. 一級未命中,查詢二級緩存
valueWrapper = secondLevelCache.get(key);
if (valueWrapper != null) {
// 3. 同步二級緩存結(jié)果到一級
firstLevelCache.put(key, valueWrapper.get());
}
}
return valueWrapper;
}
// 帶類型的緩存查詢:邏輯與 get(Object key) 一致
@Override
public <T> T get(Object key, Class<T> type) {
T value = firstLevelCache.get(key, type);
if (value == null) {
value = secondLevelCache.get(key, type);
if (value != null) {
firstLevelCache.put(key, value);
}
}
return value;
}
// 帶值加載器的查詢:優(yōu)先從一級緩存加載,失敗則從二級加載
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
try {
// 優(yōu)先從一級緩存加載
return firstLevelCache.get(key, valueLoader);
} catch (Exception e) {
// 一級緩存加載失?。ㄈ鐭o數(shù)據(jù)),從二級緩存加載
return secondLevelCache.get(key, valueLoader);
}
}
// 緩存寫入:同時寫入一級與二級緩存
@Override
public void put(Object key, Object value) {
firstLevelCache.put(key, value);
secondLevelCache.put(key, value);
}
// 緩存刪除:同時刪除一級與二級緩存的對應(yīng)鍵
@Override
public void evict(Object key) {
firstLevelCache.evict(key);
secondLevelCache.evict(key);
}
// 緩存清空:同時清空一級與二級緩存
@Override
public void clear() {
firstLevelCache.clear();
secondLevelCache.clear();
}
}
3. 配置自定義緩存管理器
@Configuration
@EnableCaching
public class CacheConfig {
public static final String CACHE_NAME = "doubleCachingCache";
// 配置自定義緩存管理器
@Bean
public CacheManager customCacheManager(RedisConnectionFactory redisConnectionFactory) {
// 1. 配置一級緩存(Caffeine)
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(
Caffeine.newBuilder()
.maximumSize(100) // 一級緩存最大條目數(shù)
.expireAfterWrite(Duration.ofMinutes(10)) // 一級緩存過期時間:10 分鐘
);
// 2. 配置二級緩存(Redis)
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 二級緩存過期時間:1 小時
)
.build();
// 3. 返回自定義緩存管理器(整合一級與二級緩存)
return new CustomCacheManager(caffeineCacheManager, redisCacheManager);
}
}
4. 業(yè)務(wù)服務(wù)使用
無需任何額外代碼,僅通過 @Cacheable 注解即可觸發(fā)完整的雙層緩存邏輯:
@Cacheable(value = CacheConfig.CACHE_NAME, key = "#id")
public String getFromCache(String id) {
return "Hello " + id;
}
核心邏輯說明
- 查詢邏輯:一級緩存命中 → 直接返回;一級未命中 → 查詢二級緩存 → 同步到一級緩存后返回。
- 寫入邏輯:數(shù)據(jù)同時寫入一級與二級緩存,確保緩存一致性。
- 刪除/清空邏輯:操作同時作用于兩級緩存,避免“一級緩存已刪、二級緩存仍存在”的臟數(shù)據(jù)問題。

五、總結(jié)
本文詳細(xì)介紹了 Spring Boot 應(yīng)用中以 Caffeine 為本地一級緩存、Redis 為分布式二級緩存的多層緩存實(shí)現(xiàn),從手動管理的粗糙方案、Spring CompositeCacheManager 的優(yōu)化方案,到自定義 CacheManager 實(shí)現(xiàn)緩存自動同步的完美方案,并搭配 TestContainers 測試驗(yàn)證,為提升應(yīng)用性能、保證緩存一致性提供了可落地的實(shí)踐指南。
到此這篇關(guān)于SpringBoot應(yīng)用中的多層緩存實(shí)踐的文章就介紹到這了,更多相關(guān)SpringBoot 多層緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用Redisson實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
這篇文章主要為大家詳細(xì)介紹了SpringBoot使用Redisson實(shí)現(xiàn)分布式鎖,秒殺系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-12-12
springboot全局配置文件與多環(huán)境配置的全過程
SpringBoot項(xiàng)目在多環(huán)境配置上表現(xiàn)的非常優(yōu)秀,只需要非常簡單的操作就可以完成配置,下面這篇文章主要給大家介紹了關(guān)于springboot全局配置文件與多環(huán)境配置的相關(guān)資料,需要的朋友可以參考下2021-12-12
ireport數(shù)據(jù)表格報(bào)表的簡單使用
這篇文章給大家介紹了如何畫一個報(bào)表模板,這里介紹下畫表格需要用到的組件,文中通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2021-10-10
SpringBoot2.0整合Shiro框架實(shí)現(xiàn)用戶權(quán)限管理的示例
這篇文章主要介紹了SpringBoot2.0整合Shiro框架實(shí)現(xiàn)用戶權(quán)限管理的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
JDK14的新特性NullPointerExceptions的使用
這篇文章主要介紹了JDK14的新特性NullPointerExceptions的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
Java使用String類格式化當(dāng)前日期實(shí)現(xiàn)代碼
這篇文章主要介紹了Java使用String類格式化當(dāng)前日期實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-02-02

