# 缓存系统指南 ## 概述 本项目采用多级缓存架构,将原有的Redis缓存替换为基于Caffeine的本地缓存,以提升性能并减少对外部Redis服务的依赖。 ## 缓存架构 ### 多级缓存设计 ``` 应用层缓存 ├── Caffeine缓存 (本地内存缓存) │ ├── 用户缓存 │ ├── 角色缓存 │ ├── 岗位缓存 │ └── 登录用户缓存 ├── Guava缓存 (配置缓存) │ ├── 系统配置 │ ├── 字典数据 │ └── 部门数据 └── Redis缓存 (分布式缓存,可选) ├── 会话缓存 ├── 验证码缓存 └── 分布式锁 ``` ## Caffeine缓存迁移 ### 1. 迁移概述 本项目已将原有的Redis缓存替换为基于Caffeine的本地缓存,主要变更包括: - **新增依赖**: Caffeine 2.9.3 - **新增组件**: AbstractCaffeineCacheTemplate、CaffeineCacheService - **缓存中心更新**: CacheCenter使用Caffeine缓存替换Redis缓存 ### 2. 缓存配置 #### 2.1 Caffeine缓存配置 ```java @Configuration public class CaffeineCacheConfig { @Bean public Cache caffeineCache() { return Caffeine.newBuilder() // 初始容量 .initialCapacity(128) // 最大容量 .maximumSize(1024) // 写入后过期时间 .expireAfterWrite(60, TimeUnit.MINUTES) // 写入后刷新时间 .refreshAfterWrite(5, TimeUnit.MINUTES) // 并发级别 .concurrencyLevel(16) // 使用软引用 .softValues() .build(); } } ``` #### 2.2 缓存服务配置 ```java @Service public class CaffeineCacheService { // 验证码缓存 @Bean public AbstractCaffeineCacheTemplate captchaCache() { return new AbstractCaffeineCacheTemplate("captcha") { @Override protected String loadData(String key) { // 从数据库加载数据的逻辑 return null; } }; } // 登录用户缓存 @Bean public AbstractCaffeineCacheTemplate loginUserCache() { return new AbstractCaffeineCacheTemplate("loginUser") { @Override protected LoginUser loadData(String key) { // 从数据库加载用户信息 return userService.getLoginUserByToken(key); } }; } } ``` ## 缓存使用 ### 1. 获取缓存数据 #### 1.1 从缓存中获取用户数据 ```java // 从缓存中获取用户数据 SysUserEntity user = CacheCenter.userCache.get("1"); // 从缓存中获取角色数据 SysRoleEntity role = CacheCenter.roleCache.get("1"); // 从缓存中获取岗位数据 SysPostEntity post = CacheCenter.postCache.get("1"); ``` #### 1.2 获取登录用户信息 ```java public LoginUser getLoginUser(String token) { LoginUser loginUser = CacheCenter.loginUserCache.get(token); if (loginUser == null) { throw new ApiException("用户登录状态已过期"); } return loginUser; } ``` ### 2. 使缓存失效 #### 2.1 使单个缓存失效 ```java // 使单个用户缓存失效 CacheCenter.userCache.invalidate("1"); // 使单个登录用户缓存失效 CacheCenter.loginUserCache.invalidate(token); ``` #### 2.2 使所有缓存失效 ```java // 使所有用户缓存失效 CacheCenter.userCache.invalidateAll(); // 使所有登录用户缓存失效 CacheCenter.loginUserCache.invalidateAll(); ``` ### 3. 更新缓存数据 ```java @Service @RequiredArgsConstructor public class UserService { public void updateUser(UpdateUserCommand command) { // 1. 更新数据库 userModel.update(command); // 2. 使缓存失效 CacheCenter.userCache.invalidate(command.getUserId().toString()); // 3. 如果用户正在登录,也需要更新登录用户缓存 String token = getTokenByUserId(command.getUserId()); if (token != null) { CacheCenter.loginUserCache.invalidate(token); } } } ``` ## 缓存模板 ### 1. AbstractCaffeineCacheTemplate ```java public abstract class AbstractCaffeineCacheTemplate { protected final Cache cache; protected final String cacheName; public AbstractCaffeineCacheTemplate(String cacheName) { this.cacheName = cacheName; this.cache = Caffeine.newBuilder() .maximumSize(1024) .expireAfterWrite(60, TimeUnit.MINUTES) .refreshAfterWrite(5, TimeUnit.MINUTES) .recordStats() .build(); } /** * 从缓存获取数据,如果不存在则从数据库加载 */ public T get(String key) { try { return cache.get(key, k -> loadData(k)); } catch (Exception e) { log.error("从缓存{}获取数据失败: {}", cacheName, e.getMessage(), e); return null; } } /** * 从数据库加载数据的抽象方法 */ protected abstract T loadData(String key); /** * 使缓存失效 */ public void invalidate(String key) { cache.invalidate(key); } /** * 使所有缓存失效 */ public void invalidateAll() { cache.invalidateAll(); } /** * 获取缓存统计信息 */ public String getStats() { CacheStats stats = cache.stats(); return String.format( "缓存%s统计: 命中率=%.2f%%, 加载次数=%d, 加载成功次数=%d, 加载异常次数=%d, 总加载时间=%dms", cacheName, stats.hitRate() * 100, stats.loadCount(), stats.loadSuccessCount(), stats.loadExceptionCount(), stats.totalLoadTime() ); } } ``` ## 缓存监控 ### 1. 通过API接口监控 访问缓存统计信息: ```bash GET /monitor/caffeine/stats ``` **响应**: ```json { "code": 200, "msg": "success", "data": { "userCache": "缓存user统计: 命中率=95.23%, 加载次数=100, 加载成功次数=98, 加载异常次数=2, 总加载时间=150ms", "roleCache": "缓存role统计: 命中率=98.76%, 加载次数=50, 加载成功次数=50, 加载异常次数=0, 总加载时间=80ms", "loginUserCache": "缓存loginUser统计: 命中率=92.45%, 加载次数=200, 加载成功次数=195, 加载异常次数=5, 总加载时间=300ms" } } ``` ### 2. 通过代码监控 ```java @Service @RequiredArgsConstructor public class CacheMonitorService { @Autowired private CaffeineCacheService caffeineCacheService; public void printCacheStats() { String stats = caffeineCacheService.getCacheStats(); System.out.println(stats); } @Scheduled(fixedRate = 300000) // 每5分钟执行一次 public void monitorCachePerformance() { log.info("缓存性能监控: {}", caffeineCacheService.getCacheStats()); } } ``` ## 性能对比 ### Redis vs Caffeine 性能对比 | 特性 | Redis | Caffeine | |------|-------|----------| | 访问延迟 | 网络延迟 (0.1-1ms) | 内存访问 (<0.01ms) | | 数据一致性 | 分布式一致 | 本地不一致 | | 内存使用 | 独立服务内存 | 应用进程内存 | | 适用场景 | 分布式缓存 | 本地高速缓存 | | 部署复杂度 | 需要独立部署 | 内置在应用中 | ### 适用场景建议 #### 使用Caffeine缓存的场景 - 用户基本信息 - 角色权限数据 - 系统配置数据 - 字典数据 - 登录用户会话 #### 使用Redis缓存的场景 - 分布式会话 - 分布式锁 - 消息队列 - 跨服务数据共享 ## 迁移注意事项 ### 1. 数据一致性 Caffeine是本地缓存,在多实例部署时需要注意: ```java @Service public class CacheConsistencyService { /** * 在多实例环境下,需要同步缓存失效 */ public void invalidateUserCache(Long userId) { // 1. 使本地缓存失效 CacheCenter.userCache.invalidate(userId.toString()); // 2. 发布缓存失效事件(如果有多实例) applicationEventPublisher.publishEvent(new UserCacheEvictEvent(userId)); } } ``` ### 2. 内存使用监控 ```java @Service public class MemoryMonitorService { @Scheduled(fixedRate = 60000) // 每1分钟执行一次 public void monitorMemoryUsage() { Runtime runtime = Runtime.getRuntime(); long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long maxMemory = runtime.maxMemory(); double usageRate = (double) usedMemory / maxMemory * 100; if (usageRate > 80) { log.warn("内存使用率过高: {}%, 建议调整缓存配置", String.format("%.2f", usageRate)); } } } ``` ### 3. 缓存配置调优 根据业务需求调整缓存参数: ```java @Configuration public class CustomCacheConfig { @Bean public AbstractCaffeineCacheTemplate userCache() { return new AbstractCaffeineCacheTemplate("user") { @Override protected SysUserEntity loadData(String key) { return userService.getById(Long.valueOf(key)); } } { @Override protected Cache createCache() { return Caffeine.newBuilder() .maximumSize(500) // 用户数据较多,限制最大数量 .expireAfterWrite(30, TimeUnit.MINUTES) // 用户数据变化较少 .build(); } }; } @Bean public AbstractCaffeineCacheTemplate loginUserCache() { return new AbstractCaffeineCacheTemplate("loginUser") { @Override protected LoginUser loadData(String key) { return userService.getLoginUserByToken(key); } } { @Override protected Cache createCache() { return Caffeine.newBuilder() .maximumSize(1000) // 登录用户较多 .expireAfterWrite(2, TimeUnit.HOURS) // 登录会话较长 .build(); } }; } } ``` ## 故障排查 ### 1. 常见问题 #### 1.1 缓存不生效 - 检查 `@PostConstruct` 方法是否正确初始化 - 验证缓存键是否正确 - 检查缓存配置参数 #### 1.2 内存溢出 - 调整 `maximumSize` 参数 - 缩短 `expireAfterWrite` 时间 - 监控内存使用情况 #### 1.3 数据不一致 - 确保数据更新时正确调用 `invalidate` 方法 - 在多实例环境下实现缓存同步 - 定期清理过期缓存 ### 2. 调试方法 ```java // 查看缓存统计 System.out.println(CacheCenter.userCache.getStats()); // 手动触发缓存加载 CacheCenter.userCache.get("test-key"); // 查看缓存内容(仅开发环境) CacheCenter.userCache.asMap().forEach((key, value) -> { System.out.println(key + ": " + value); }); ``` ## 回滚方案 如需回滚到Redis缓存: 1. 恢复 `CacheCenter` 中的Redis缓存引用 2. 移除Caffeine相关依赖 3. 重启应用 ```xml ``` ## 最佳实践 ### 1. 缓存键设计 - 使用有意义的键名 - 避免过长的键名 - 统一命名规范 ### 2. 缓存失效策略 - 写操作时立即失效相关缓存 - 读操作时设置合理的过期时间 - 定期清理无用缓存 ### 3. 性能监控 - 监控缓存命中率 - 监控内存使用情况 - 监控缓存加载时间 ### 4. 安全考虑 - 避免缓存敏感数据 - 设置合理的缓存大小 - 实现缓存数据加密