Skip to content

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缓存空间名(必填,推荐使用常量)
keySpEL 表达式,如 #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);
}

⚠️ 注意valuekey 应与 @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" 防穿透
更新后刷新@CachePutKey 必须与查询一致
删除/修改@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。