# 缓存策略详解 ## 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 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() .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 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 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 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 zrange(String key, long start, long end) { return stringRedisTemplate.opsForZSet().range(key, start, end); } /** * 获取分数范围 */ public Set 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 executeWithLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit, Supplier 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 getCacheStats() { Map stats = new HashMap<>(); Collection 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 get(String key, Class type, Supplier 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 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. **内存泄漏**: 设置合理的过期时间和大小限制