Spring 缓存策略深度解析
缓存是提升系统性能的核心手段,Spring 提供了统一的缓存抽象层。
Spring Cache 核心注解
| 注解 | 作用 | 使用场景 |
|---|---|---|
| @Cacheable | 查询缓存 | 读操作 |
| @CachePut | 更新缓存 | 写操作后刷新 |
| @CacheEvict | 删除缓存 | 数据删除时 |
| @Caching | 组合操作 | 复杂缓存逻辑 |
基础配置
启用缓存
Java
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
缓存配置类
Java
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
@Cacheable 详解
基本使用
Java
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
@Cacheable(value = "users", key = "#name", condition = "#name.length() > 3")
public User findByName(String name) {
return userRepository.findByName(name);
}
}
Key 表达式
| 表达式 | 说明 |
|---|---|
| #id | 参数名为 id |
| #p0 | 第一个参数 |
| #user.id | 参数对象的属性 |
| #root.methodName | 方法名 |
@CachePut 与 @CacheEvict
更新缓存
Java
@Service
public class UserService {
@CachePut(value = "users", key = "#user.id")
public User update(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void delete(Long id) {
userRepository.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true)
public void clearAll() {
// 清空所有缓存
}
}
缓存问题解决方案
1. 缓存穿透
Java
// 方案:缓存空值
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User findById(Long id) {
User user = userRepository.findById(id).orElse(null);
if (user == null) {
// 缓存空对象,设置较短过期时间
cacheTemplate.opsForValue().set("users:null:" + id, "", 5, TimeUnit.MINUTES);
}
return user;
}
2. 缓存击穿
Java
// 方案:互斥锁
@Cacheable(value = "users", key = "#id")
public User findByIdWithLock(Long id) {
String lockKey = "lock:users:" + id;
try {
// 尝试获取锁
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(acquired)) {
User user = userRepository.findById(id).orElse(null);
if (user != null) {
redisTemplate.opsForValue().set("users:" + id, user, 30, TimeUnit.MINUTES);
}
return user;
} else {
Thread.sleep(100);
return findById(id);
}
} finally {
redisTemplate.delete(lockKey);
}
}
3. 缓存雪崩
Java
// 方案:随机过期时间
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30).plusSeconds(ThreadLocalRandom.current().nextInt(300)));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
多级缓存架构
Caffeine + Redis 二级缓存
Java
@Configuration
public class MultiCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// L1: 本地缓存 Caffeine
CaffeineCacheManager caffeineManager = new CaffeineCacheManager();
caffeineManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES));
// L2: 分布式缓存 Redis
RedisCacheManager redisManager = RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)))
.build();
return new CompositeCacheManager(caffeineManager, redisManager);
}
}
缓存注解属性详解
@Cacheable 属性
Java
@Cacheable(
value = "users", // 缓存名称
key = "#id", // 缓存 key
condition = "#id > 0", // 缓存条件
unless = "#result == null", // 排除条件
cacheManager = "customCacheManager" // 指定缓存管理器
)
public User findById(Long id) { ... }
自定义 Key 生成器
Java
@Bean
public KeyGenerator customKeyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName());
sb.append(":").append(method.getName());
for (Object param : params) {
sb.append(":").append(param.toString());
}
return sb.toString();
};
}
@Cacheable(value = "users", keyGenerator = "customKeyGenerator")
public User findById(Long id) { ... }
要点总结
| 要点 | 说明 |
|---|---|
| 缓存注解 | @Cacheable/@CachePut/@CacheEvict |
| Key设计 | 业务唯一标识,避免冲突 |
| 穿透防护 | 缓存空值 + 布隆过滤器 |
| 击穿防护 | 互斥锁 + 热点数据永不过期 |
| 雪崩防护 | 随机过期时间 + 熔断降级 |
| 多级缓存 | Caffeine(L1) + Redis(L2) |
📝 发现内容有误?点击此处直接编辑