shop-back-end/doc/缓存系统指南.md

472 lines
12 KiB
Markdown
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服务的依赖。
## 缓存架构
### 多级缓存设计
```
应用层缓存
├── 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<String, Object> 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<String> captchaCache() {
return new AbstractCaffeineCacheTemplate<String>("captcha") {
@Override
protected String loadData(String key) {
// 从数据库加载数据的逻辑
return null;
}
};
}
// 登录用户缓存
@Bean
public AbstractCaffeineCacheTemplate<LoginUser> loginUserCache() {
return new AbstractCaffeineCacheTemplate<LoginUser>("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<T> {
protected final Cache<String, T> 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<SysUserEntity> userCache() {
return new AbstractCaffeineCacheTemplate<SysUserEntity>("user") {
@Override
protected SysUserEntity loadData(String key) {
return userService.getById(Long.valueOf(key));
}
} {
@Override
protected Cache<String, SysUserEntity> createCache() {
return Caffeine.newBuilder()
.maximumSize(500) // 用户数据较多,限制最大数量
.expireAfterWrite(30, TimeUnit.MINUTES) // 用户数据变化较少
.build();
}
};
}
@Bean
public AbstractCaffeineCacheTemplate<LoginUser> loginUserCache() {
return new AbstractCaffeineCacheTemplate<LoginUser>("loginUser") {
@Override
protected LoginUser loadData(String key) {
return userService.getLoginUserByToken(key);
}
} {
@Override
protected Cache<String, LoginUser> 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
<!-- 移除Caffeine依赖 -->
<!-- <dependency> -->
<!-- <groupId>com.github.ben-manes.caffeine</groupId> -->
<!-- <artifactId>caffeine</artifactId> -->
<!-- <version>2.9.3</version> -->
<!-- </dependency> -->
```
## 最佳实践
### 1. 缓存键设计
- 使用有意义的键名
- 避免过长的键名
- 统一命名规范
### 2. 缓存失效策略
- 写操作时立即失效相关缓存
- 读操作时设置合理的过期时间
- 定期清理无用缓存
### 3. 性能监控
- 监控缓存命中率
- 监控内存使用情况
- 监控缓存加载时间
### 4. 安全考虑
- 避免缓存敏感数据
- 设置合理的缓存大小
- 实现缓存数据加密