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




























