Files
ruoyi-vue-pro/.cursor/rules/cache-strategy.mdc

665 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 缓存策略详解
## Redis 缓存架构
### 缓存层次结构
```
应用层 -> 本地缓存 (Caffeine) -> Redis 缓存 -> 数据库
```
### 核心组件
- **Spring Cache**: 统一缓存抽象
- **Redis**: 分布式缓存存储
- **Caffeine**: 本地缓存提升性能
- **Redisson**: Redis 客户端和分布式锁
## 缓存配置
### Redis 连接配置
```yaml
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
timeout: 2000ms
lettuce:
pool:
max-active: 200
max-idle: 20
min-idle: 5
max-wait: 1000ms
```
### 缓存管理器配置
```java
@Configuration
@EnableCaching
public class CacheConfiguration {
@Bean
@Primary
public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
// Redis 缓存管理器
RedisCacheManager.Builder builder = RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(redisTemplate.getConnectionFactory())
.cacheDefaults(buildCacheConfiguration());
return builder.build();
}
@Bean("localCacheManager")
public CacheManager localCacheManager() {
// 本地缓存管理器
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES));
return cacheManager;
}
}
```
## 缓存使用模式
### 基础缓存注解
```java
@Service
public class UserServiceImpl implements UserService {
/**
* 查询缓存
*/
@Cacheable(value = "user", key = "#id")
public UserDO getUser(Long id) {
return userMapper.selectById(id);
}
/**
* 更新缓存
*/
@CachePut(value = "user", key = "#user.id")
public UserDO updateUser(UserDO user) {
userMapper.updateById(user);
return user;
}
/**
* 删除缓存
*/
@CacheEvict(value = "user", key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
/**
* 清空缓存
*/
@CacheEvict(value = "user", allEntries = true)
public void clearUserCache() {
// 清空所有用户缓存
}
}
```
### 条件缓存
```java
// 条件缓存 - 只有状态为启用的用户才缓存
@Cacheable(value = "user", key = "#id", condition = "#user.status == 1")
public UserDO getUser(Long id) {
return userMapper.selectById(id);
}
// 排除缓存 - 管理员用户不缓存
@Cacheable(value = "user", key = "#id", unless = "#result.username == 'admin'")
public UserDO getUser(Long id) {
return userMapper.selectById(id);
}
```
### 自定义缓存键
```java
// 复合键
@Cacheable(value = "user", key = "#tenantId + ':' + #username")
public UserDO getUserByUsername(Long tenantId, String username) {
return userMapper.selectOne(new LambdaQueryWrapper<UserDO>()
.eq(UserDO::getTenantId, tenantId)
.eq(UserDO::getUsername, username));
}
// 使用 SpEL 表达式
@Cacheable(value = "user", key = "T(String).valueOf(#user.tenantId).concat(':').concat(#user.username)")
public UserDO createUser(UserCreateReqVO reqVO) {
// 创建用户逻辑
}
```
## Redis 数据结构使用
### 字符串 (String)
```java
@Component
public class StringCacheService {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 设置缓存
*/
public void set(String key, String value, Duration timeout) {
stringRedisTemplate.opsForValue().set(key, value, timeout);
}
/**
* 获取缓存
*/
public String get(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 原子递增
*/
public Long increment(String key) {
return stringRedisTemplate.opsForValue().increment(key);
}
}
```
### 哈希 (Hash)
```java
@Component
public class HashCacheService {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 设置 Hash 字段
*/
public void hset(String key, String field, String value) {
stringRedisTemplate.opsForHash().put(key, field, value);
}
/**
* 获取 Hash 字段
*/
public String hget(String key, String field) {
return (String) stringRedisTemplate.opsForHash().get(key, field);
}
/**
* 获取所有 Hash 字段
*/
public Map<Object, Object> hgetAll(String key) {
return stringRedisTemplate.opsForHash().entries(key);
}
}
```
### 列表 (List)
```java
@Component
public class ListCacheService {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 左推入
*/
public void lpush(String key, String value) {
stringRedisTemplate.opsForList().leftPush(key, value);
}
/**
* 右弹出
*/
public String rpop(String key) {
return stringRedisTemplate.opsForList().rightPop(key);
}
/**
* 获取列表
*/
public List<String> range(String key, long start, long end) {
return stringRedisTemplate.opsForList().range(key, start, end);
}
}
```
### 集合 (Set)
```java
@Component
public class SetCacheService {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 添加成员
*/
public void sadd(String key, String... values) {
stringRedisTemplate.opsForSet().add(key, values);
}
/**
* 获取所有成员
*/
public Set<String> smembers(String key) {
return stringRedisTemplate.opsForSet().members(key);
}
/**
* 判断是否存在
*/
public Boolean sismember(String key, String value) {
return stringRedisTemplate.opsForSet().isMember(key, value);
}
}
```
### 有序集合 (ZSet)
```java
@Component
public class ZSetCacheService {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 添加成员
*/
public void zadd(String key, String value, double score) {
stringRedisTemplate.opsForZSet().add(key, value, score);
}
/**
* 获取排名范围
*/
public Set<String> zrange(String key, long start, long end) {
return stringRedisTemplate.opsForZSet().range(key, start, end);
}
/**
* 获取分数范围
*/
public Set<String> zrangeByScore(String key, double min, double max) {
return stringRedisTemplate.opsForZSet().rangeByScore(key, min, max);
}
}
```
## 缓存键设计规范
### 键命名规范
```java
public class RedisKeyConstants {
/**
* 用户缓存
* KEY 格式user:{id}
* 过期时间30 分钟
*/
public static final String USER = "user";
/**
* 验证码缓存
* KEY 格式captcha:{uuid}
* 过期时间5 分钟
*/
public static final String CAPTCHA = "captcha";
/**
* 权限缓存
* KEY 格式permission:{userId}
* 过期时间1 小时
*/
public static final String PERMISSION = "permission";
/**
* 字典缓存
* KEY 格式dict:{type}
* 过期时间:永不过期
*/
public static final String DICT = "dict";
}
```
### 键生成工具
```java
@Component
public class RedisKeyBuilder {
/**
* 构建用户缓存键
*/
public static String buildUserKey(Long userId) {
return RedisKeyConstants.USER + ":" + userId;
}
/**
* 构建验证码缓存键
*/
public static String buildCaptchaKey(String uuid) {
return RedisKeyConstants.CAPTCHA + ":" + uuid;
}
/**
* 构建权限缓存键
*/
public static String buildPermissionKey(Long userId) {
return RedisKeyConstants.PERMISSION + ":" + userId;
}
/**
* 构建租户级别缓存键
*/
public static String buildTenantKey(String prefix, Long tenantId, Object suffix) {
return prefix + ":" + tenantId + ":" + suffix;
}
}
```
## 分布式锁
### Redisson 分布式锁
```java
@Component
public class DistributedLockService {
@Resource
private RedissonClient redissonClient;
/**
* 尝试获取锁
*/
public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 释放锁
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
/**
* 锁模板方法
*/
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit, Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(waitTime, leaseTime, unit)) {
return supplier.get();
} else {
throw new ServiceException("获取锁失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ServiceException("获取锁被中断");
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
```
### 分布式锁应用
```java
@Service
public class OrderService {
@Resource
private DistributedLockService lockService;
/**
* 创建订单(防重复提交)
*/
public OrderDO createOrder(OrderCreateReqVO reqVO) {
String lockKey = "order:create:" + reqVO.getUserId();
return lockService.executeWithLock(lockKey, 5, 10, TimeUnit.SECONDS, () -> {
// 检查重复订单
validateDuplicateOrder(reqVO);
// 创建订单
return doCreateOrder(reqVO);
});
}
}
```
## 缓存预热
### 应用启动预热
```java
@Component
public class CacheWarmUpService {
@Resource
private DictDataService dictDataService;
@Resource
private ConfigService configService;
@EventListener(ApplicationReadyEvent.class)
public void warmUpCache() {
log.info("[warmUpCache][开始预热缓存]");
// 预热字典缓存
dictDataService.warmUpCache();
// 预热配置缓存
configService.warmUpCache();
log.info("[warmUpCache][预热缓存完成]");
}
}
```
### 定时预热
```java
@Component
public class CacheScheduleService {
/**
* 定时刷新热点数据
*/
@Scheduled(fixedRate = 300000) // 5分钟执行一次
public void refreshHotData() {
// 刷新热点商品缓存
refreshProductCache();
// 刷新活动缓存
refreshActivityCache();
}
}
```
## 缓存监控
### 缓存统计
```java
@Component
public class CacheMetricsService {
@Resource
private CacheManager cacheManager;
/**
* 获取缓存统计信息
*/
public Map<String, Object> getCacheStats() {
Map<String, Object> stats = new HashMap<>();
Collection<String> cacheNames = cacheManager.getCacheNames();
for (String cacheName : cacheNames) {
Cache cache = cacheManager.getCache(cacheName);
if (cache instanceof RedisCache) {
// 获取 Redis 缓存统计
stats.put(cacheName, getRedisCacheStats(cache));
}
}
return stats;
}
}
```
### 缓存健康检查
```java
@Component
public class CacheHealthIndicator extends AbstractHealthIndicator {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
try {
// 测试 Redis 连接
String pong = stringRedisTemplate.getConnectionFactory()
.getConnection().ping();
if ("PONG".equals(pong)) {
builder.up().withDetail("redis", "连接正常");
} else {
builder.down().withDetail("redis", "连接异常");
}
} catch (Exception e) {
builder.down().withException(e);
}
}
}
```
## 缓存优化策略
### 多级缓存
```java
@Service
public class MultiLevelCacheService {
@Resource
private CacheManager localCacheManager;
@Resource
private CacheManager redisCacheManager;
public <T> T get(String key, Class<T> type, Supplier<T> loader) {
// 1. 先查本地缓存
Cache localCache = localCacheManager.getCache("local");
T value = localCache.get(key, type);
if (value != null) {
return value;
}
// 2. 再查 Redis 缓存
Cache redisCache = redisCacheManager.getCache("redis");
value = redisCache.get(key, type);
if (value != null) {
// 回写本地缓存
localCache.put(key, value);
return value;
}
// 3. 最后查数据库
value = loader.get();
if (value != null) {
// 写入两级缓存
localCache.put(key, value);
redisCache.put(key, value);
}
return value;
}
}
```
### 缓存穿透防护
```java
@Service
public class BloomFilterService {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 检查是否可能存在
*/
public boolean mightContain(String key) {
// 使用布隆过滤器检查
String bloomKey = "bloom:user";
return redisTemplate.opsForValue().getBit(bloomKey, hash(key));
}
/**
* 添加到布隆过滤器
*/
public void put(String key) {
String bloomKey = "bloom:user";
redisTemplate.opsForValue().setBit(bloomKey, hash(key), true);
}
private long hash(String key) {
// 简单的哈希算法
return Math.abs(key.hashCode()) % 1000000;
}
}
```
### 缓存雪崩防护
```java
@Service
public class AntiAvalancheService {
/**
* 随机过期时间防止雪崩
*/
@Cacheable(value = "user", key = "#id")
public UserDO getUser(Long id) {
// 缓存时间30分钟 + 随机0-10分钟
Duration timeout = Duration.ofMinutes(30 + new Random().nextInt(10));
UserDO user = userMapper.selectById(id);
// 手动设置过期时间
redisTemplate.expire("user:" + id, timeout);
return user;
}
}
```
## 最佳实践
### 缓存设计原则
1. **热点数据优先**: 经常访问的数据才缓存
2. **合理的过期时间**: 根据业务场景设置过期时间
3. **缓存键规范**: 使用有意义的键名和统一的命名规范
4. **避免大对象**: 单个缓存对象不要太大
5. **监控告警**: 设置缓存命中率等监控指标
### 常见问题解决
1. **缓存击穿**: 使用分布式锁保护热点数据
2. **缓存穿透**: 使用布隆过滤器或缓存空值
3. **缓存雪崩**: 设置随机过期时间
4. **数据一致性**: 使用缓存更新策略和事务
5. **内存泄漏**: 设置合理的过期时间和大小限制