文章目录
-
- [1. 缓存概述](#1. 缓存概述)
-
- [1.1 缓存类型](#1.1 缓存类型)
- [1.2 缓存策略](#1.2 缓存策略)
- [1.3 核心依赖](#1.3 核心依赖)
- [2. Spring Cache注解](#2. Spring Cache注解)
-
- [2.1 基础缓存注解](#2.1 基础缓存注解)
- [2.2 条件缓存](#2.2 条件缓存)
- [3. 缓存配置](#3. 缓存配置)
-
- [3.1 基础缓存配置](#3.1 基础缓存配置)
- [3.2 Caffeine缓存配置](#3.2 Caffeine缓存配置)
- [3.3 Redis缓存配置](#3.3 Redis缓存配置)
- [4. 多级缓存](#4. 多级缓存)
-
- [4.1 多级缓存实现](#4.1 多级缓存实现)
- [4.2 缓存预热](#4.2 缓存预热)
- [5. 缓存监控](#5. 缓存监控)
-
- [5.1 缓存统计](#5.1 缓存统计)
- [5.2 缓存监控端点](#5.2 缓存监控端点)
- [6. 缓存策略](#6. 缓存策略)
-
- [6.1 缓存更新策略](#6.1 缓存更新策略)
- [6.2 缓存一致性](#6.2 缓存一致性)
- [7. 缓存性能优化](#7. 缓存性能优化)
-
- [7.1 缓存预热](#7.1 缓存预热)
- [7.2 缓存压缩](#7.2 缓存压缩)
- [8. 缓存配置优化](#8. 缓存配置优化)
-
- [8.1 配置文件](#8.1 配置文件)
- [8.2 高级缓存配置](#8.2 高级缓存配置)
- [9. 总结](#9. 总结)
1. 缓存概述
缓存是提高应用性能的重要手段,Spring Boot提供了完整的缓存解决方案。通过缓存可以减少数据库访问、提高响应速度、降低系统负载。
1.1 缓存类型
- 本地缓存:JVM内存中的缓存,速度快但容量有限
- 分布式缓存:Redis、Memcached等,支持集群部署
- 数据库缓存:查询结果缓存、连接池缓存
- HTTP缓存:浏览器缓存、CDN缓存
1.2 缓存策略
- Cache-Aside:应用程序管理缓存
- Read-Through:缓存自动从数据源读取
- Write-Through:同时写入缓存和数据源
- Write-Behind:异步写入数据源
1.3 核心依赖
xml
<dependencies>
<!-- Spring Boot Cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Spring Boot Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Caffeine Cache -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- EhCache -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
</dependencies>
2. Spring Cache注解
2.1 基础缓存注解
java
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UserCacheService {
@Autowired
private UserRepository userRepository;
// 缓存查询结果
@Cacheable(value = "users", key = "#id")
public Optional<User> findById(Long id) {
System.out.println("从数据库查询用户: " + id);
return userRepository.findById(id);
}
// 缓存用户列表
@Cacheable(value = "userList", key = "#status")
public List<User> findByStatus(String status) {
System.out.println("从数据库查询用户列表: " + status);
return userRepository.findByStatus(status);
}
// 更新缓存
@CachePut(value = "users", key = "#user.id")
public User save(User user) {
System.out.println("保存用户到数据库: " + user.getUsername());
return userRepository.save(user);
}
// 清除缓存
@CacheEvict(value = "users", key = "#id")
public void deleteById(Long id) {
System.out.println("从数据库删除用户: " + id);
userRepository.deleteById(id);
}
// 清除所有用户缓存
@CacheEvict(value = "users", allEntries = true)
public void clearAllUserCache() {
System.out.println("清除所有用户缓存");
}
// 组合缓存操作
@Caching(
evict = {
@CacheEvict(value = "users", key = "#user.id"),
@CacheEvict(value = "userList", allEntries = true)
}
)
public User updateUser(User user) {
System.out.println("更新用户: " + user.getUsername());
return userRepository.save(user);
}
}
2.2 条件缓存
java
package com.example.demo.service;
import com.example.demo.entity.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
@Service
public class ConditionalCacheService {
// 条件缓存:只有活跃用户才缓存
@Cacheable(value = "activeUsers", condition = "#user.status == 'ACTIVE'")
public User cacheActiveUser(User user) {
System.out.println("缓存活跃用户: " + user.getUsername());
return user;
}
// 条件清除:只有特定状态才清除
@CacheEvict(value = "users", condition = "#user.status == 'INACTIVE'")
public void evictInactiveUser(User user) {
System.out.println("清除非活跃用户缓存: " + user.getUsername());
}
// 条件更新:只有特定条件才更新缓存
@CachePut(value = "users", unless = "#result == null")
public User updateUserConditionally(User user) {
if (user.getId() == null) {
return null; // 不缓存null结果
}
return user;
}
}
3. 缓存配置
3.1 基础缓存配置
java
package com.example.demo.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setCacheNames("users", "userList", "activeUsers");
return cacheManager;
}
}
3.2 Caffeine缓存配置
java
package com.example.demo.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CaffeineCacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.expireAfterAccess(5, TimeUnit.MINUTES)
.recordStats());
return cacheManager;
}
}
3.3 Redis缓存配置
java
package com.example.demo.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
public class RedisCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
// 配置序列化
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
// 配置缓存
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
4. 多级缓存
4.1 多级缓存实现
java
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Service
public class MultiLevelCacheService {
@Autowired
private UserRepository userRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String REDIS_KEY_PREFIX = "user:";
private static final long REDIS_EXPIRE_TIME = 30; // 30分钟
// L1缓存:本地缓存
@Cacheable(value = "localUsers", key = "#id")
public Optional<User> getUserFromL1Cache(Long id) {
System.out.println("L1缓存未命中,查询L2缓存");
return getUserFromL2Cache(id);
}
// L2缓存:Redis缓存
public Optional<User> getUserFromL2Cache(Long id) {
String redisKey = REDIS_KEY_PREFIX + id;
User user = (User) redisTemplate.opsForValue().get(redisKey);
if (user != null) {
System.out.println("L2缓存命中: " + id);
return Optional.of(user);
}
System.out.println("L2缓存未命中,查询数据库");
return getUserFromDatabase(id);
}
// L3缓存:数据库
public Optional<User> getUserFromDatabase(Long id) {
System.out.println("从数据库查询用户: " + id);
Optional<User> user = userRepository.findById(id);
if (user.isPresent()) {
// 写入L2缓存
String redisKey = REDIS_KEY_PREFIX + id;
redisTemplate.opsForValue().set(redisKey, user.get(), REDIS_EXPIRE_TIME, TimeUnit.MINUTES);
}
return user;
}
}
4.2 缓存预热
java
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
@Service
public class CacheWarmupService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserCacheService userCacheService;
@PostConstruct
public void warmupCache() {
System.out.println("开始缓存预热...");
// 预热活跃用户缓存
List<User> activeUsers = userRepository.findByStatus("ACTIVE");
for (User user : activeUsers) {
userCacheService.findById(user.getId());
}
System.out.println("缓存预热完成,预热了 " + activeUsers.size() + " 个用户");
}
}
5. 缓存监控
5.1 缓存统计
java
package com.example.demo.service;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class CacheStatsService {
@Autowired
private CacheManager cacheManager;
public Map<String, Object> getCacheStats() {
Map<String, Object> stats = new HashMap<>();
cacheManager.getCacheNames().forEach(cacheName -> {
CaffeineCache caffeineCache = (CaffeineCache) cacheManager.getCache(cacheName);
if (caffeineCache != null) {
Cache<Object, Object> nativeCache = caffeineCache.getNativeCache();
CacheStats cacheStats = nativeCache.stats();
Map<String, Object> cacheStatsMap = new HashMap<>();
cacheStatsMap.put("hitCount", cacheStats.hitCount());
cacheStatsMap.put("missCount", cacheStats.missCount());
cacheStatsMap.put("hitRate", cacheStats.hitRate());
cacheStatsMap.put("evictionCount", cacheStats.evictionCount());
cacheStatsMap.put("size", nativeCache.estimatedSize());
stats.put(cacheName, cacheStatsMap);
}
});
return stats;
}
}
5.2 缓存监控端点
java
package com.example.demo.controller;
import com.example.demo.service.CacheStatsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/api/cache")
public class CacheController {
@Autowired
private CacheStatsService cacheStatsService;
@GetMapping("/stats")
public Map<String, Object> getCacheStats() {
return cacheStatsService.getCacheStats();
}
}
6. 缓存策略
6.1 缓存更新策略
java
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CacheUpdateStrategyService {
@Autowired
private UserRepository userRepository;
// 写回策略:同时更新缓存和数据库
@CachePut(value = "users", key = "#user.id")
@Transactional
public User writeThrough(User user) {
System.out.println("写回策略:同时更新缓存和数据库");
return userRepository.save(user);
}
// 写分配策略:先更新数据库,再更新缓存
@Transactional
public User writeAllocate(User user) {
System.out.println("写分配策略:先更新数据库");
User savedUser = userRepository.save(user);
// 手动更新缓存
updateCache(savedUser);
return savedUser;
}
// 写不分配策略:只更新数据库,清除缓存
@CacheEvict(value = "users", key = "#user.id")
@Transactional
public User writeNoAllocate(User user) {
System.out.println("写不分配策略:只更新数据库,清除缓存");
return userRepository.save(user);
}
// 异步写回策略
@Transactional
public User writeBehind(User user) {
System.out.println("异步写回策略:先更新缓存,异步更新数据库");
updateCache(user);
// 异步更新数据库
asyncUpdateDatabase(user);
return user;
}
private void updateCache(User user) {
// 实现缓存更新逻辑
}
private void asyncUpdateDatabase(User user) {
// 实现异步数据库更新逻辑
}
}
6.2 缓存一致性
java
package com.example.demo.service;
import com.example.demo.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class CacheConsistencyService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 强一致性:使用分布式锁
public User updateUserWithLock(User user) {
String lockKey = "lock:user:" + user.getId();
String lockValue = String.valueOf(System.currentTimeMillis());
try {
// 获取分布式锁
Boolean lockAcquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, 30, TimeUnit.SECONDS);
if (lockAcquired) {
// 更新数据库
updateDatabase(user);
// 更新缓存
updateCache(user);
return user;
} else {
throw new RuntimeException("获取锁失败");
}
} finally {
// 释放锁
releaseLock(lockKey, lockValue);
}
}
// 最终一致性:使用消息队列
public User updateUserWithMQ(User user) {
// 更新数据库
updateDatabase(user);
// 发送缓存更新消息
sendCacheUpdateMessage(user);
return user;
}
private void updateDatabase(User user) {
// 实现数据库更新逻辑
}
private void updateCache(User user) {
// 实现缓存更新逻辑
}
private void sendCacheUpdateMessage(User user) {
// 实现消息发送逻辑
}
private void releaseLock(String lockKey, String lockValue) {
// 实现锁释放逻辑
}
}
7. 缓存性能优化
7.1 缓存预热
java
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Service
public class CacheWarmupService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserCacheService userCacheService;
// 异步预热缓存
@Async
public CompletableFuture<Void> warmupCacheAsync() {
System.out.println("开始异步缓存预热...");
List<User> users = userRepository.findAll();
for (User user : users) {
userCacheService.findById(user.getId());
}
System.out.println("异步缓存预热完成");
return CompletableFuture.completedFuture(null);
}
// 分批预热缓存
public void warmupCacheInBatches(int batchSize) {
System.out.println("开始分批缓存预热...");
List<User> users = userRepository.findAll();
for (int i = 0; i < users.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, users.size());
List<User> batch = users.subList(i, endIndex);
for (User user : batch) {
userCacheService.findById(user.getId());
}
System.out.println("预热批次 " + (i / batchSize + 1) + " 完成");
}
System.out.println("分批缓存预热完成");
}
}
7.2 缓存压缩
java
package com.example.demo.service;
import com.example.demo.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@Service
public class CacheCompressionService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 压缩存储
public void storeCompressed(String key, User user) {
try {
byte[] compressed = compress(user);
redisTemplate.opsForValue().set(key, compressed);
} catch (Exception e) {
throw new RuntimeException("压缩存储失败", e);
}
}
// 解压读取
public User getCompressed(String key) {
try {
byte[] compressed = (byte[]) redisTemplate.opsForValue().get(key);
if (compressed != null) {
return decompress(compressed);
}
return null;
} catch (Exception e) {
throw new RuntimeException("解压读取失败", e);
}
}
private byte[] compress(User user) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(baos);
ObjectOutputStream oos = new ObjectOutputStream(gzos);
oos.writeObject(user);
oos.close();
return baos.toByteArray();
}
private User decompress(byte[] compressed) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(compressed);
GZIPInputStream gzis = new GZIPInputStream(bais);
ObjectInputStream ois = new ObjectInputStream(gzis);
User user = (User) ois.readObject();
ois.close();
return user;
}
}
8. 缓存配置优化
8.1 配置文件
yaml
# application.yml
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=1000,expireAfterWrite=10m,expireAfterAccess=5m
redis:
time-to-live: 600000 # 10分钟
cache-null-values: false
use-key-prefix: true
key-prefix: "cache:"
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
# 缓存监控
management:
endpoints:
web:
exposure:
include: cache,health,metrics
endpoint:
cache:
enabled: true
metrics:
export:
prometheus:
enabled: true
8.2 高级缓存配置
java
package com.example.demo.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCaching
public class AdvancedCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
// 配置不同的缓存策略
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
// 用户缓存:10分钟过期
cacheConfigurations.put("users", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())));
// 用户列表缓存:5分钟过期
cacheConfigurations.put("userList", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())));
// 活跃用户缓存:30分钟过期
cacheConfigurations.put("activeUsers", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())))
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
}
9. 总结
Spring Boot缓存机制提供了完整的缓存解决方案:
- 缓存注解:@Cacheable、@CachePut、@CacheEvict等
- 缓存配置:支持多种缓存实现(Caffeine、Redis、EhCache)
- 多级缓存:本地缓存+分布式缓存
- 缓存监控:缓存统计和性能监控
- 缓存策略:多种缓存更新策略
- 性能优化:缓存预热、压缩、异步处理
- 配置优化:灵活的缓存配置
通过合理使用这些缓存特性,可以显著提高应用性能和用户体验。