
在本文中,我们将深入探讨 Spring Boot 应用中多层缓存的实现思路。具体而言,我们会采用本地一级缓存(L1) 与远程分布式二级缓存(L2) 的组合方案:其中一级缓存使用较短的过期时间,二级缓存则配置较长的过期时间。
多层缓存的核心逻辑是优先查询本地缓存,若本地缓存未命中,则再查询二级缓存。其核心目标是通过减少与远程服务的往返通信次数,显著提升应用性能。
需要说明的是,本文的实现方案具备通用性——只需轻微调整,即可适配 Ehcache、Hazelcast 等其他缓存组件。
要在 Spring Boot 项目中使用 Caffeine,需先引入对应的依赖。推荐通过 Spring Boot 官方的依赖管理(spring-boot-dependencies)控制版本,避免版本冲突;若未使用 Spring Boot 父工程,则需手动指定 Caffeine 版本。
在 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 作为一级缓存,核心原因在于其卓越的性能与灵活性:
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 提供的 CompositeCacheManager(组合缓存管理器)可将多个缓存组件整合,大幅简化双层缓存的配置逻辑。以下是具体实现:
@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();
}
}
只需在方法上添加 @Cacheable 注解,即可自动触发双层缓存逻辑:
@Cacheable(value = "myCache", key = "#id")
public String getFromCache(String id) {
return "Hello " + id;
}
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;
}
显然,这种“配置+手动代码”的混合模式仍不完美。接下来,我们将通过自定义缓存管理器,实现真正无缝的双层缓存同步。
通过实现 Spring 的 CacheManager 接口与 Cache 接口,我们可以完全掌控双层缓存的读写、同步与过期逻辑,实现“一次注解,双向同步”。
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();
}
}
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();
}
}
@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);
}
}
无需任何额外代码,仅通过 @Cacheable 注解即可触发完整的双层缓存逻辑:
@Cacheable(value = CacheConfig.CACHE_NAME, key = "#id")
public String getFromCache(String id) {
return "Hello " + id;
}
以下图表展示了完整的操作流程:

本文详细介绍了 Spring Boot 应用中以 Caffeine 为本地一级缓存、Redis 为分布式二级缓存的多层缓存实现,从手动管理的粗糙方案、Spring CompositeCacheManager 的优化方案,到自定义 CacheManager 实现缓存自动同步的完美方案。为提升应用性能、保证缓存一致性提供了可落地的实践指南。