📋 学习目标
- 理解缓存概念 - 缓存原理与应用场景
- 掌握Spring Cache - 注解式缓存使用
- 集成Redis缓存 - 分布式缓存方案
- 缓存策略配置 - 过期时间、淘汰策略
- 缓存最佳实践 - 缓存穿透、雪崩处理
- 性能优化技巧 - 缓存预热、监控
🔍 缓存核心概念
- 缓存命中 - 数据在缓存中找到
- 缓存穿透 - 查询不存在的数据
- 缓存雪崩 - 大量缓存同时失效
- 缓存击穿 - 热点数据缓存失效
- 缓存预热 - 提前加载热点数据
- 缓存更新 - 数据一致性保证
📦 缓存依赖配置
<!-- pom.xml 缓存依赖配置 -->
<dependencies>
<!-- SpringBoot Cache启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- SpringBoot Redis启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- SpringBoot Web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
# application.yml 缓存配置
spring:
# Redis配置
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
# 缓存配置
cache:
type: redis
redis:
time-to-live: 600000 # 10分钟
cache-null-values: true
use-key-prefix: true
key-prefix: "cache:"
# 日志配置
logging:
level:
cn.bugstack.springframework.boot.cache: DEBUG
org.springframework.cache: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
🎯 Spring Cache注解使用
// CacheConfig.java - 缓存配置类
@Configuration
@EnableCaching
@Slf4j
public class CacheConfig {
/**
* 配置Redis缓存管理器
*/
@Bean
@Primary
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
// 设置缓存过期时间为10分钟
.entryTtl(Duration.ofMinutes(10))
// 设置key序列化方式
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
// 设置value序列化方式
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
// 不缓存null值
.disableCachingNullValues();
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
}
/**
* 配置本地缓存管理器(Caffeine)
*/
@Bean("caffeineCacheManager")
public CacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
// 设置最大缓存数量
.maximumSize(1000)
// 设置写入后过期时间
.expireAfterWrite(Duration.ofMinutes(5))
// 设置缓存移除监听器
.removalListener((key, value, cause) ->
log.info("缓存移除 - key: {}, value: {}, cause: {}", key, value, cause))
);
return cacheManager;
}
}
// UserService.java - 用户服务类
@Service
@Slf4j
public class UserService {
@Autowired
private UserRepository userRepository;
/**
* @Cacheable - 缓存查询结果
* value: 缓存名称
* key: 缓存key,支持SpEL表达式
* condition: 缓存条件
* unless: 排除条件
*/
@Cacheable(value = "users", key = "#id", condition = "#id > 0")
public User getUserById(Long id) {
log.info("从数据库查询用户信息,ID: {}", id);
// 模拟数据库查询耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
User user = userRepository.findById(id).orElse(null);
if (user == null) {
log.warn("用户不存在,ID: {}", id);
}
return user;
}
/**
* @CachePut - 更新缓存
* 无论缓存是否存在,都会执行方法并更新缓存
*/
@CachePut(value = "users", key = "#user.id")
public User saveUser(User user) {
log.info("保存用户信息: {}", user);
if (user.getId() == null) {
user.setId(System.currentTimeMillis());
}
user.setUpdateTime(LocalDateTime.now());
User savedUser = userRepository.save(user);
log.info("用户保存成功,更新缓存: {}", savedUser);
return savedUser;
}
/**
* @CacheEvict - 清除缓存
* allEntries: 是否清除所有缓存条目
* beforeInvocation: 是否在方法执行前清除缓存
*/
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
log.info("删除用户,ID: {}", id);
userRepository.deleteById(id);
log.info("用户删除成功,清除缓存: {}", id);
}
/**
* @CacheEvict - 清除所有用户缓存
*/
@CacheEvict(value = "users", allEntries = true)
public void clearAllUserCache() {
log.info("清除所有用户缓存");
}
/**
* @Caching - 组合多个缓存操作
*/
@Caching(
cacheable = @Cacheable(value = "userStats", key = "#userId"),
put = @CachePut(value = "userLastAccess", key = "#userId")
)
public UserStats getUserStats(Long userId) {
log.info("获取用户统计信息,ID: {}", userId);
// 模拟复杂统计计算
UserStats stats = new UserStats();
stats.setUserId(userId);
stats.setLoginCount(100);
stats.setLastLoginTime(LocalDateTime.now());
stats.setTotalPoints(1500);
return stats;
}
}
// User.java - 用户实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String email;
private String nickname;
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@PrePersist
protected void onCreate() {
createTime = LocalDateTime.now();
updateTime = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updateTime = LocalDateTime.now();
}
}
// UserStats.java - 用户统计实体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserStats implements Serializable {
private Long userId;
private Integer loginCount;
private LocalDateTime lastLoginTime;
private Integer totalPoints;
}
🎨 缓存架构图示
L1 Cache
本地缓存
→
L2 Cache
Redis缓存
→
Database
数据库
多级缓存架构:本地缓存 → Redis缓存 → 数据库
提供更好的性能和可用性保障
📍 缓存注解
- @Cacheable - 缓存查询结果
- @CachePut - 更新缓存数据
- @CacheEvict - 清除缓存数据
- @Caching - 组合多个缓存操作
- @CacheConfig - 类级别缓存配置
- @EnableCaching - 启用缓存功能
📢 缓存策略
- Cache-Aside - 旁路缓存模式
- Read-Through - 读穿透模式
- Write-Through - 写穿透模式
- Write-Behind - 写回模式
- Refresh-Ahead - 预刷新模式
- TTL策略 - 时间过期策略
🔧 自定义缓存管理
// CustomCacheManager.java - 自定义缓存管理器
@Component
@Slf4j
public class CustomCacheManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 设置缓存
*/
public void set(String key, Object value, long timeout, TimeUnit unit) {
try {
redisTemplate.opsForValue().set(key, value, timeout, unit);
log.debug("设置缓存成功 - key: {}, timeout: {}{}", key, timeout, unit);
} catch (Exception e) {
log.error("设置缓存失败 - key: {}", key, e);
}
}
/**
* 获取缓存
*/
@SuppressWarnings("unchecked")
public <T> T get(String key, Class<T> clazz) {
try {
Object value = redisTemplate.opsForValue().get(key);
if (value == null) {
log.debug("缓存未命中 - key: {}", key);
return null;
}
log.debug("缓存命中 - key: {}", key);
return clazz.isInstance(value) ? (T) value : null;
} catch (Exception e) {
log.error("获取缓存失败 - key: {}", key, e);
return null;
}
}
/**
* 删除缓存
*/
public boolean delete(String key) {
try {
Boolean result = redisTemplate.delete(key);
log.debug("删除缓存 - key: {}, result: {}", key, result);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("删除缓存失败 - key: {}", key, e);
return false;
}
}
/**
* 批量删除缓存
*/
public long deletePattern(String pattern) {
try {
Set<String> keys = redisTemplate.keys(pattern);
if (keys != null && !keys.isEmpty()) {
Long result = redisTemplate.delete(keys);
log.debug("批量删除缓存 - pattern: {}, count: {}", pattern, result);
return result != null ? result : 0;
}
return 0;
} catch (Exception e) {
log.error("批量删除缓存失败 - pattern: {}", pattern, e);
return 0;
}
}
/**
* 检查缓存是否存在
*/
public boolean exists(String key) {
try {
Boolean result = redisTemplate.hasKey(key);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("检查缓存存在性失败 - key: {}", key, e);
return false;
}
}
/**
* 设置缓存过期时间
*/
public boolean expire(String key, long timeout, TimeUnit unit) {
try {
Boolean result = redisTemplate.expire(key, timeout, unit);
log.debug("设置缓存过期时间 - key: {}, timeout: {}{}", key, timeout, unit);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("设置缓存过期时间失败 - key: {}", key, e);
return false;
}
}
/**
* 获取缓存剩余过期时间
*/
public long getExpire(String key, TimeUnit unit) {
try {
Long expire = redisTemplate.getExpire(key, unit);
return expire != null ? expire : -1;
} catch (Exception e) {
log.error("获取缓存过期时间失败 - key: {}", key, e);
return -1;
}
}
}
// CacheService.java - 缓存服务类
@Service
@Slf4j
public class CacheService {
@Autowired
private CustomCacheManager cacheManager;
private static final String USER_CACHE_PREFIX = "user:";
private static final String PRODUCT_CACHE_PREFIX = "product:";
/**
* 缓存用户信息
*/
public void cacheUser(User user) {
String key = USER_CACHE_PREFIX + user.getId();
cacheManager.set(key, user, 30, TimeUnit.MINUTES);
}
/**
* 获取缓存的用户信息
*/
public User getCachedUser(Long userId) {
String key = USER_CACHE_PREFIX + userId;
return cacheManager.get(key, User.class);
}
/**
* 清除用户缓存
*/
public void evictUser(Long userId) {
String key = USER_CACHE_PREFIX + userId;
cacheManager.delete(key);
}
/**
* 清除所有用户缓存
*/
public void evictAllUsers() {
String pattern = USER_CACHE_PREFIX + "*";
long count = cacheManager.deletePattern(pattern);
log.info("清除所有用户缓存,数量: {}", count);
}
/**
* 缓存预热 - 预加载热点数据
*/
@PostConstruct
public void warmUpCache() {
log.info("开始缓存预热...");
// 预加载热点用户数据
List<Long> hotUserIds = Arrays.asList(1L, 2L, 3L, 4L, 5L);
for (Long userId : hotUserIds) {
try {
// 这里应该从数据库加载用户数据
User user = loadUserFromDatabase(userId);
if (user != null) {
cacheUser(user);
log.debug("预热用户缓存: {}", userId);
}
} catch (Exception e) {
log.error("预热用户缓存失败: {}", userId, e);
}
}
log.info("缓存预热完成");
}
/**
* 从数据库加载用户数据(模拟)
*/
private User loadUserFromDatabase(Long userId) {
// 模拟数据库查询
User user = new User();
user.setId(userId);
user.setUsername("user" + userId);
user.setEmail("user" + userId + "@example.com");
user.setCreateTime(LocalDateTime.now());
return user;
}
}
🛡️ 缓存问题解决方案
// CacheProblemSolver.java - 缓存问题解决方案
@Service
@Slf4j
public class CacheProblemSolver {
@Autowired
private CustomCacheManager cacheManager;
@Autowired
private UserRepository userRepository;
/**
* 解决缓存穿透问题
* 使用布隆过滤器或缓存空值
*/
public User getUserWithBloomFilter(Long userId) {
String key = "user:" + userId;
// 1. 先检查缓存
User cachedUser = cacheManager.get(key, User.class);
if (cachedUser != null) {
return cachedUser;
}
// 2. 检查布隆过滤器(这里简化为ID范围检查)
if (userId <= 0 || userId > 1000000) {
log.warn("用户ID超出范围,可能是恶意请求: {}", userId);
// 缓存空值,防止重复查询
cacheManager.set(key, new User(), 5, TimeUnit.MINUTES);
return null;
}
// 3. 查询数据库
User user = userRepository.findById(userId).orElse(null);
// 4. 缓存结果(包括空值)
if (user != null) {
cacheManager.set(key, user, 30, TimeUnit.MINUTES);
} else {
// 缓存空值,设置较短过期时间
cacheManager.set(key, new User(), 5, TimeUnit.MINUTES);
}
return user;
}
/**
* 解决缓存雪崩问题
* 使用随机过期时间
*/
public User getUserWithRandomExpire(Long userId) {
String key = "user:" + userId;
User cachedUser = cacheManager.get(key, User.class);
if (cachedUser != null) {
return cachedUser;
}
User user = userRepository.findById(userId).orElse(null);
if (user != null) {
// 设置随机过期时间,避免同时失效
int randomMinutes = 30 + new Random().nextInt(30); // 30-60分钟
cacheManager.set(key, user, randomMinutes, TimeUnit.MINUTES);
}
return user;
}
/**
* 解决缓存击穿问题
* 使用分布式锁
*/
public User getUserWithDistributedLock(Long userId) {
String key = "user:" + userId;
String lockKey = "lock:user:" + userId;
// 1. 先检查缓存
User cachedUser = cacheManager.get(key, User.class);
if (cachedUser != null) {
return cachedUser;
}
// 2. 尝试获取分布式锁
boolean lockAcquired = acquireDistributedLock(lockKey, 10, TimeUnit.SECONDS);
if (!lockAcquired) {
// 获取锁失败,等待一段时间后重试
try {
Thread.sleep(100);
return cacheManager.get(key, User.class);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
try {
// 3. 双重检查缓存
cachedUser = cacheManager.get(key, User.class);
if (cachedUser != null) {
return cachedUser;
}
// 4. 查询数据库
User user = userRepository.findById(userId).orElse(null);
// 5. 更新缓存
if (user != null) {
cacheManager.set(key, user, 30, TimeUnit.MINUTES);
}
return user;
} finally {
// 6. 释放分布式锁
releaseDistributedLock(lockKey);
}
}
/**
* 获取分布式锁(简化实现)
*/
private boolean acquireDistributedLock(String lockKey, long timeout, TimeUnit unit) {
try {
String lockValue = UUID.randomUUID().toString();
Boolean result = cacheManager.redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, timeout, unit);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("获取分布式锁失败: {}", lockKey, e);
return false;
}
}
/**
* 释放分布式锁
*/
private void releaseDistributedLock(String lockKey) {
try {
cacheManager.delete(lockKey);
} catch (Exception e) {
log.error("释放分布式锁失败: {}", lockKey, e);
}
}
/**
* 缓存更新策略 - 延时双删
*/
@Transactional
public User updateUserWithDelayedDoubleDelete(User user) {
String key = "user:" + user.getId();
// 1. 删除缓存
cacheManager.delete(key);
// 2. 更新数据库
User updatedUser = userRepository.save(user);
// 3. 延时删除缓存(异步执行)
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000); // 延时1秒
cacheManager.delete(key);
log.debug("延时双删完成: {}", key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("延时双删被中断: {}", key);
}
});
return updatedUser;
}
}
📊 缓存方案对比
缓存类型 |
适用场景 |
优点 |
缺点 |
本地缓存 |
单机应用、读多写少 |
速度快、无网络开销 |
不支持分布式、内存限制 |
Redis缓存 |
分布式应用、高并发 |
支持分布式、功能丰富 |
网络开销、单点故障 |
多级缓存 |
高性能要求、大规模应用 |
性能最优、可用性高 |
复杂度高、一致性难保证 |
CDN缓存 |
静态资源、全球分发 |
就近访问、减轻服务器压力 |
只适用静态内容 |
💡 缓存最佳实践
🎯 设计原则
合理设置过期时间 - 根据数据特性设置TTL
避免缓存穿透 - 使用布隆过滤器或缓存空值
防止缓存雪崩 - 设置随机过期时间
解决缓存击穿 - 使用分布式锁或互斥锁
保证数据一致性 - 选择合适的缓存更新策略
// CacheMonitor.java - 缓存监控
@Component
@Slf4j
public class CacheMonitor {
@Autowired
private CacheManager cacheManager;
/**
* 缓存统计信息
*/
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void logCacheStats() {
if (cacheManager instanceof RedisCacheManager) {
Collection<String> cacheNames = cacheManager.getCacheNames();
for (String cacheName : cacheNames) {
Cache cache = cacheManager.getCache(cacheName);
if (cache instanceof RedisCache) {
log.info("缓存统计 - 缓存名: {}", cacheName);
// 这里可以添加更详细的统计信息
}
}
}
}
/**
* 缓存健康检查
*/
@EventListener
public void handleCacheEvent(CacheEvent event) {
log.debug("缓存事件: {}", event);
}
}
// 缓存配置建议
@Configuration
public class CacheOptimizationConfig {
/**
* 配置缓存键生成器
*/
@Bean
public KeyGenerator customKeyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName()).append(".");
sb.append(method.getName()).append("(");
for (Object param : params) {
sb.append(param.toString()).append(",");
}
if (params.length > 0) {
sb.setLength(sb.length() - 1);
}
sb.append(")");
return sb.toString();
};
}
/**
* 配置缓存错误处理器
*/
@Bean
public CacheErrorHandler cacheErrorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception,
Cache cache, Object key) {
log.error("缓存获取错误 - cache: {}, key: {}",
cache.getName(), key, exception);
}
@Override
public void handleCachePutError(RuntimeException exception,
Cache cache, Object key, Object value) {
log.error("缓存存储错误 - cache: {}, key: {}",
cache.getName(), key, exception);
}
@Override
public void handleCacheEvictError(RuntimeException exception,
Cache cache, Object key) {
log.error("缓存清除错误 - cache: {}, key: {}",
cache.getName(), key, exception);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.error("缓存清空错误 - cache: {}", cache.getName(), exception);
}
};
}
}