JavaFree 多级缓存框架
核心组件:Caffeine(本地缓存) + Redis(远程缓存) + Resilience4j(熔断降级) + Redis Pub/Sub(缓存同步)
一、为什么需要多级缓存?
在高并发系统中,仅依赖 Redis 作为一级缓存存在以下问题:
- ❌ 网络开销大:每次缓存访问需跨网络调用 Redis。
- ❌ Redis 成为性能瓶颈:大量请求集中打向 Redis,易导致连接池耗尽或响应延迟。
- ❌ 单点故障风险:Redis 故障将直接影响业务可用性。
- ❌ Spring Cache 功能受限:默认仅支持单一缓存实现,无法组合本地与分布式缓存。
✅ 多级缓存的优势
| 优势 | 说明 |
|---|---|
| ⚡ 极致性能 | Caffeine 本地缓存命中率达微秒级,显著降低平均响应时间 |
| 📉 大幅减压 | 减少 80%+ 对 Redis 的访问量,提升系统吞吐能力 |
| 🔗 数据一致 | 基于 Redis Pub/Sub 实现集群内本地缓存自动同步 |
| 💪 高可用保障 | 集成 Resilience4j 熔断器,Redis 异常时自动降级为只读本地缓存 |
| 🧩 灵活扩展 | 支持自定义 Key 生成器、过期策略、命名规范与监控治理 |
二、架构设计概览
plaintext
+------------------+ ←→ +------------------+
| Application | | Redis |
| (Caffeine Cache) | ←─Pub/Sub─| (Central Cache) |
+------------------+ +------------------+
↑
L1: Local Cache L2: Remote Cache- L1 缓存(本地):基于 Caffeine,无网络开销,适合高频小数据。
- L2 缓存(远程):基于 Redis,容量大、共享性强,保证全局一致性。
- 同步机制:通过 Redis 发布/订阅(Pub/Sub)通知其他实例刷新本地缓存。
- 容错机制:集成 Resilience4j,Redis 异常时自动熔断并降级。
三、快速接入四步法
步骤 1️⃣:引入依赖
✅ 若已使用
javafree-cloud-starter,可跳过此步。
xml
<dependency>
<groupId>cn.javafree.cloud.cache</groupId>
<artifactId>javafree-cloud-cache</artifactId>
</dependency>步骤 2️⃣:配置多级缓存(YAML)
单机 Redis 示例(开发/测试环境)
yaml
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password: your_strong_password
timeout: 2500ms
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 5
max-wait: 5000ms
cache:
type: redis
multilevel:
enable-redis: true
time-to-live: 30m
use-key-prefix: true
key-prefix: "JAVAFREECACHE:DEV"
topic: "cache:multilevel:topic"
allow-null-values: true
local:
max-size: 2000
initial-capacity: 1000
expire-mode: RANDOM
expire-after-write: 1800s
expire-after-access: 1800s
expiry-jitter: 50
circuit-breaker:
failure-rate-threshold: 35
slow-call-rate-threshold: 30
slow-call-duration-threshold: 500ms
sliding-window-type: time_based
sliding-window-size: 60
minimum-number-of-calls: 50
permitted-number-of-calls-in-half-open-state: 30
wait-duration-in-open-state: 30s
max-wait-duration-in-half-open-state: 10s
register-health-indicator: true
event-consumer-buffer-size: 50🔔 生产建议:
- 使用 Redis 集群模式(见附录)
key-prefix格式:{项目名}:{环境},如USER-SERVICE:PROD- 密码必须强复杂度,禁止明文写入 Git
步骤 3️⃣:注册缓存键元信息(用于监控)
创建类实现 CacheKeyProvider 接口,集中管理缓存空间:
java
@Component
public class UserCacheKeys implements CacheKeyProvider {
public static final String USER_DTOS = "USER_DTOS";
public static final String USERPAGES = "USERPAGES";
@Override
public Map<String, String> getCacheKeys() {
return Map.of(
USER_DTOS, "用户DTO对象缓存(含密码信息)",
USERPAGES, "用户分页查询结果缓存"
);
}
}✅ 关键要求:
- 必须标注
@Component- 键为常量字符串,值为中文描述
- 注册后可在 缓存治理平台 查看、清理、分析缓存
四、核心注解使用指南
4.1 @Cacheable —— 查询缓存
java
@Cacheable(value = UserCacheKeys.USER_DTOS, key = "#id")
public User getUserById(String id) {
return userDao.findById(id).orElse(null);
}| 参数 | 说明 |
|---|---|
value | 缓存空间名(必填,推荐使用常量) |
key | SpEL 表达式,如 #id、#p0、#root.methodName |
condition | 满足条件才缓存,如 #id != null |
unless | 满足条件不缓存,如 #result == null(防穿透) |
4.2 @CachePut —— 更新缓存
java
@CachePut(value = UserCacheKeys.USER_DTOS, key = "#user.id")
public User updateUser(User user) {
return userDao.save(user);
}⚠️ 注意:
value和key应与@Cacheable保持一致。
4.3 @CacheEvict —— 清除缓存
java
// 清除单条
@CacheEvict(value = UserCacheKeys.USER_DTOS, key = "#id")
public void deleteUser(String id) { ... }
// 清空整个空间
@CacheEvict(value = UserCacheKeys.USERPAGES, allEntries = true)
public void clearUserPages() { ... }| 参数 | 说明 |
|---|---|
allEntries | 是否清空整个缓存空间 |
beforeInvocation | 是否在方法执行前清除(即使异常也清除) |
4.4 @Caching —— 组合操作
java
@Caching(
cacheable = @Cacheable(value = "USERS", key = "#user.id"),
put = @CachePut(value = "USERS", key = "#user.id"),
evict = @CacheEvict(value = "USERPAGES", allEntries = true)
)
public User saveUser(User user) {
return userDao.save(user);
}五、高级用法:分页查询缓存
问题背景
多条件 + 分页 + 排序的查询,参数组合爆炸,直接缓存易失效。
解决方案:自定义 Key 生成器
参数结构示例:
json
{
"dataParam": { "username": "test" },
"pageParam": {
"currentPage": 1,
"pageSize": 10,
"sorts": [{ "property": "userOrder", "direction": "asc" }]
}
}Key 生成规则:
java
target.getClass().getSimpleName() + "_" +
method.getName() + "_" +
MD5(JSON.stringify(allParams))使用方式:
java
@Cacheable(value = UserCacheKeys.USER_DTOS, keyGenerator = "customKeyGenerator")
public PageResult<User> findUsersByUserAny(User user, PageParam pageParam) {
// 查询逻辑
}✅ 效果:相同查询条件 → 相同 Key → 高效命中缓存
六、缓存管理与监控
6.1 缓存治理平台
系统提供可视化缓存管理页面,支持:
| 字段 | 说明 |
|---|---|
| 缓存名称 | 如 USER_DTOS |
| 描述 | “用户DTO对象缓存” |
| 本地缓存数 | 当前 JVM 中缓存条目数 |
| Redis 缓存数 | Redis 中该空间对象数量 |
| 操作 | 【清空】按钮 |
6.2 手动清空缓存
适用场景:
- 修改菜单后部分用户未生效 → 清空
menuCache - 权限变更未同步 → 清空
authCache
⚠️ 建议:在业务低峰期操作,首次访问会重建缓存,略有延迟。
七、最佳实践总结
| 场景 | 推荐注解 | 注意事项 |
|---|---|---|
| 查询数据 | @Cacheable | 配合 unless = "#result == null" 防穿透 |
| 更新后刷新 | @CachePut | Key 必须与查询一致 |
| 删除/修改 | @CacheEvict(allEntries=true) | 清空整个空间更安全 |
| 复杂逻辑 | @Caching | 组合多种操作 |
| 分页查询 | @Cacheable + customKeyGenerator | 保证参数一致性 |
八、附录:Redis 集群配置示例
yaml
spring:
data:
redis:
password: your_cluster_password
cluster:
nodes:
- 10.10.8.170:5001
- 10.10.8.170:5002
- 10.10.8.171:5001
- 10.10.8.171:5002
- 10.10.8.172:5001
- 10.10.8.172:5002
max-redirects: 3
timeout: 2000ms
lettuce:
pool:
max-active: 16
min-idle: 5
cluster:
refresh:
adaptive: true
period: 2000九、常见问题 FAQ
Q1:Redis 不可用时,服务是否会启动失败?
A:不会。设置 enable-redis: false 可绕过 Redis 依赖,仅使用本地缓存。
Q2:如何防止缓存雪崩?
A:使用 expire-mode: RANDOM + expiry-jitter: 50,使过期时间随机化(±50%)。
Q3:缓存穿透如何防护?
A:开启 allow-null-values: true,将 null 结果也缓存,避免重复查库。
Q4:集群环境下本地缓存如何同步?
A:通过 Redis Pub/Sub 自动广播失效消息,各节点监听并清除本地对应 Key。