分类
标签
1510 字
8 分钟
在 Springboot 中配置可开关的缓存
SpringBoot项目中构建可开关的Redis和Caffeine缓存系统
在现代Web应用中,缓存是提升系统性能的重要手段。特别是在处理大规模数据时,合理的缓存策略可以显著减少数据库压力,提高响应速度。本文将详细介绍如何在SpringBoot项目中构建一个灵活可配置的Redis和Caffeine双重缓存系统,重点介绍”可开关”机制的实现。
背景与挑战
在Aristotle知识图谱中,需要处理数百万个节点和关系的数据。面对这样的数据规模,我们遇到了两个主要挑战:
- 长查询时间:即使是小规模的子图扩展操作,在大数据量下也可能导致查询缓慢
- 查询超时:由于内存不足导致的查询超时问题
为了解决这些问题,我们设计了一个双层缓存架构:使用Caffeine作为本地缓存,Redis作为分布式缓存。
架构设计
缓存层次结构
┌─────────────────┐
│ 应用层 │
├─────────────────┤
│ Caffeine │ ← 本地缓存(一级缓存)
│ (本地内存) │
├─────────────────┤
│ Redis │ ← 分布式缓存(二级缓存)
│ (远程存储) │
├─────────────────┤
│ Neo4j │ ← 数据源
│ (数据库) │
└─────────────────┘
缓存策略
- Caffeine缓存:用于存储频繁访问的图数据,提供最快的访问速度
- Redis缓存:用于存储预计算的子图数据,支持分布式访问
- 可配置开关:支持独立开启/关闭每种缓存类型
可开关机制的核心实现
1. 配置驱动的开关控制
配置文件设计
spring:
read-cache:
enabled: true # Caffeine缓存开关
num-subgraphs: 500 # 缓存条目数量限制
redis:
enabled: false # Redis缓存开关
host: 127.0.0.1
port: 6379
配置类实现
Caffeine配置类:通过@Value
注解读取配置,根据enabled
属性决定是否创建有效缓存
@Configuration
public class CaffeineConfig {
@Value("${spring.read-cache.enabled:true}")
private boolean cacheEnabled;
@Bean
public Cache<String, GraphVO> graphCache() {
if (!cacheEnabled) {
// 返回空缓存,实际不存储任何数据
return Caffeine.newBuilder().build();
}
// 返回正常配置的缓存
return Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(2, TimeUnit.MINUTES)
.build();
}
}
Redis配置类:使用@ConditionalOnProperty
注解,只有当spring.redis.enabled=true
时才创建Redis相关Bean
@Configuration
@ConditionalOnProperty(value = "spring.redis.enabled", havingValue = "true")
public class RedisConfig {
// Redis配置只在启用时才生效
}
自动配置排除:当Redis禁用时,排除Redis自动配置
@Configuration
@ConditionalOnProperty(value = "spring.redis.enabled", havingValue = "false")
@EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class})
public class ExcludeAutoConfiguration {
}
2. 工具类的开关控制
Caffeine缓存工具
@Component
public class CaffeineCacheUtil {
@Value("${spring.read-cache.enabled}")
private boolean cacheEnabled;
public Optional<GraphVO> getCache(final String key) {
if (cacheEnabled) {
// 执行缓存查询
return Optional.ofNullable(graphCache.getIfPresent(key));
}
return Optional.empty(); // 缓存禁用时直接返回空
}
public void setCache(final String key, final GraphVO graphVO) {
if (cacheEnabled) {
// 执行缓存存储
graphCache.put(key, graphVO);
}
// 缓存禁用时什么都不做
}
}
Redis缓存工具
@Component
@ConditionalOnProperty(name = "spring.redis.enabled", havingValue = "true")
public class RedisCacheUtil {
@Value("${spring.redis.enabled}")
private boolean cacheEnabled;
public <T> T getCacheObject(final String key) {
if (!cacheEnabled) {
return null;
}
return redisTemplate.opsForValue().get(key);
}
public <T> void setCacheObject(final String key, final T value) {
if (!cacheEnabled) {
return;
}
redisTemplate.opsForValue().set(key, value);
}
}
3. 服务层的优雅降级
在服务层中,我们使用@Autowired(required = false)
来注入Redis缓存工具,这样当Redis禁用时不会报错:
@Service
public class GraphServiceImpl implements GraphService {
@Autowired
private CaffeineCacheUtil caffeineCache;
@Autowired(required = false) // 关键:Redis禁用时不会注入
private RedisCacheUtil redisCache;
@Override
public GraphVO getGraphVOByFilterParams(final FilterQueryGraphDTO dto) {
final String cacheKey = generateCacheKey(dto.getUuid(), dto.getPageNumber(), dto.getPageSize());
// 1. 首先检查Caffeine缓存
final Optional<GraphVO> cachedGraphVO = caffeineCache.getCache(cacheKey);
if (cachedGraphVO.isPresent()) {
return cachedGraphVO.get();
}
// 2. 然后检查Redis缓存(如果启用)
if (redisCache != null && redisCache.getCacheObject(cacheKey) != null) {
return redisCache.getCacheObject(cacheKey);
}
// 3. 从数据库获取数据
final GraphVO graphVO = fetchFromDatabase(dto);
// 4. 同时缓存到两个缓存(如果启用)
caffeineCache.setCache(cacheKey, graphVO);
if (redisCache != null) {
redisCache.setCacheObject(cacheKey, graphVO, 30, TimeUnit.MINUTES);
}
return graphVO;
}
}
可开关机制的优势
1. 灵活的部署策略
开发环境:仅使用Caffeine缓存,避免Redis依赖
spring:
read-cache:
enabled: true
redis:
enabled: false
测试环境:仅使用Redis缓存,测试分布式场景
spring:
read-cache:
enabled: false
redis:
enabled: true
生产环境:同时使用两种缓存,获得最佳性能
spring:
read-cache:
enabled: true
redis:
enabled: true
2. 故障隔离
- Redis故障时:系统自动降级到Caffeine缓存,保证基本功能可用
- 内存不足时:可以临时禁用Caffeine缓存,仅使用Redis
- 网络问题时:可以禁用Redis,避免连接超时影响性能
3. 性能调优
- 高并发场景:启用Caffeine缓存,减少网络开销
- 大数据量场景:启用Redis缓存,利用分布式存储
- 内存受限场景:调整缓存大小或禁用部分缓存
配置示例
不同场景的配置
仅使用Caffeine缓存
spring:
read-cache:
enabled: true
num-subgraphs: 1000
redis:
enabled: false
仅使用Redis缓存
spring:
read-cache:
enabled: false
redis:
enabled: true
host: redis-server
port: 6379
同时使用两种缓存
spring:
read-cache:
enabled: true
size: 1GB
redis:
enabled: true
host: redis-server
port: 6379
实现要点总结
1. 配置驱动
- 使用
@Value
注解读取配置开关 - 使用
@ConditionalOnProperty
控制Bean创建 - 使用
@EnableAutoConfiguration(exclude = {...})
排除不需要的自动配置
2. 优雅降级
- 使用
@Autowired(required = false)
避免依赖注入失败 - 在方法中检查开关状态,禁用时直接返回
- 提供多级缓存策略,确保系统可用性
3. 一致性保证
- 配置开关与Bean创建保持一致
- 缓存操作与开关状态同步
- 避免配置不一致导致的问题
总结
通过这种可开关的缓存设计,我们实现了:
- 灵活性:支持独立配置每种缓存类型,适应不同部署环境
- 可靠性:故障时自动降级,保证系统可用性
- 可维护性:清晰的配置管理,便于运维和调试
- 可扩展性:易于添加新的缓存策略和优化
这种设计不仅适用于知识图谱项目,也可以推广到其他需要高性能缓存的SpringBoot应用中。通过合理的开关机制,我们可以根据实际需求灵活调整缓存策略,提供更好的服务体验。
在 Springboot 中配置可开关的缓存
https://doom9527.github.io/blog/posts/ariticle-05/cache-doc/