Add comprehensive documentation for system architecture and best practices
This commit is contained in:
665
.cursor/rules/cache-strategy.mdc
Normal file
665
.cursor/rules/cache-strategy.mdc
Normal file
@@ -0,0 +1,665 @@
|
||||
# 缓存策略详解
|
||||
|
||||
## 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. **内存泄漏**: 设置合理的过期时间和大小限制
|
Reference in New Issue
Block a user