4. 本地缓存方案(Caffeine):Caffeine缓存原理、集成Spring Boot、配置过期策略与最大容量

4.1 Caffeine 缓存核心原理

Caffeine 是当前 Java 生态中性能最优的本地缓存库,其设计基于 W-TinyLFU(Window Tiny Least Frequently Used) 淘汰算法。该算法在传统 LRU(最近最少使用)和 LFU(最不经常使用)之间取得了极佳的平衡,能够有效应对突发流量与热点数据倾斜。

核心设计理念: Caffeine 通过“窗口 + 分段计数”机制,既保留了 LRU 对突发流量的快速响应能力,又利用 LFU 的长期频率统计过滤掉“冷数据”,从而在 Redis 热点削峰场景中作为第一道防线,大幅降低对后端 Redis 的冲击。
Caffeine W-TinyLFU 淘汰算法架构 Caffeine W-TinyLFU 淘汰算法架构 窗口缓存区 Window Cache 容量占比: 1% LRU 策略 主缓存区 Main Cache 容量占比: 99% SLRU 策略 淘汰门 Eviction Gate 频率阈值过滤 Count-Min Sketch 数据流说明 新数据 → 窗口缓存区(LRU) → 晋升 → 主缓存区(SLRU) → 淘汰门(频率过滤) → 淘汰 特性优势 • 高命中率 • 低内存开销 • 抗扫描攻击 应用场景 • 热点数据缓存 • 降级兜底缓存 • 本地一级缓存 性能指标 • O(1) 读写 • 纳秒级延迟 • 无锁并发

4.2 集成 Spring Boot

在 Spring Boot 项目中集成 Caffeine 缓存,推荐使用 Spring Cache 抽象层 + Caffeine 实现的组合方式。这种方式能够保持缓存代码与具体实现解耦,便于后续切换缓存方案。

4.2.1 Maven 依赖配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>

4.2.2 启用缓存与配置类

@Configuration
@EnableCaching
public class CaffeineCacheConfig {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        // 设置默认缓存规格
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(500)
                .expireAfterWrite(60, TimeUnit.SECONDS)
                .recordStats());
        // 支持按名称自定义配置
        cacheManager.setCacheNames(Arrays.asList("hotDataCache", "userCache"));
        return cacheManager;
    }
}
最佳实践: 在 Redis 热点削峰场景中,建议将 Caffeine 作为一级缓存(本地缓存),Redis 作为二级缓存(分布式缓存)。查询时先查 Caffeine,未命中再查 Redis,最后回源数据库。这种“两级缓存”架构能够将热点请求拦截在应用进程内,减少网络 I/O 开销。

4.2.3 使用 @Cacheable 注解

@Service
public class ProductService {

    @Cacheable(value = "hotDataCache", key = "#productId",
               unless = "#result == null")
    public Product getProductById(Long productId) {
        // 模拟数据库查询
        return productRepository.findById(productId).orElse(null);
    }
}

4.3 配置过期策略与最大容量

Caffeine 提供了丰富的过期策略和容量控制参数,合理配置这些参数是保证缓存命中率与内存安全的关键。

配置参数 方法 说明 推荐值(热点削峰)
初始容量 initialCapacity() 预分配哈希表大小,减少扩容开销 100 ~ 1000
最大容量 maximumSize() 基于条目数量的上限,超出后触发淘汰 500 ~ 5000(视 JVM 堆内存而定)
最大权重 maximumWeight() 基于自定义权重的上限,适合大小不一的缓存对象 按业务对象大小估算
写入后过期 expireAfterWrite() 写入后固定时长过期,适合一致性要求高的场景 30s ~ 300s
访问后过期 expireAfterAccess() 最后一次访问后固定时长过期,适合热点数据 60s ~ 600s
自定义过期 expireAfter() 基于 Expiry 接口实现动态过期时间 按业务逻辑定制
弱引用键 weakKeys() 使用 WeakReference 存储键,允许 GC 回收 谨慎使用,可能影响命中率
软引用值 softValues() 使用 SoftReference 存储值,内存不足时 GC 回收 适合大对象缓存

4.3.1 典型配置示例

@Bean
public Cache hotDataCache() {
    return Caffeine.newBuilder()
            .initialCapacity(200)
            .maximumSize(1000)
            .expireAfterWrite(120, TimeUnit.SECONDS)
            .expireAfterAccess(60, TimeUnit.SECONDS)
            .recordStats()
            .build();
}
容量规划警告: 在 Redis 热点削峰场景中,Caffeine 的最大容量必须根据 JVM 堆内存和业务数据量进行精确估算。建议将 Caffeine 缓存容量控制在堆内存的 5%~10% 以内,避免因本地缓存过大导致 Full GC 频繁或 OOM。生产环境建议通过 -Xmx 参数限制堆内存上限,并配合 recordStats() 监控缓存命中率。

4.3.2 过期策略对比

策略 淘汰触发条件 一致性 命中率 适用场景
写入后过期 写入时刻 + 固定时长 高 中 数据更新频率固定,如配置信息
访问后过期 最后访问时刻 + 固定时长 中 高 热点数据,如商品详情
自定义过期 根据业务逻辑动态计算 灵活 最高 复杂业务,如不同商品不同过期时间
最大容量淘汰 条目数超过 maximumSize — — 内存保护,必须配置
Caffeine 缓存过期与淘汰决策流程 Caffeine 缓存过期与淘汰决策流程 请求到达缓存 检查是否过期 过期 → 淘汰 未过期 → 返回数据 容量是否超限 说明:过期策略优先于容量淘汰;容量超限时触发 W-TinyLFU 算法淘汰低频数据

4.4 缓存统计与监控

启用 recordStats() 后,可以通过 CaffeineCache 的 stats() 方法获取缓存运行指标,用于评估缓存效果和调整参数。

// 获取缓存统计信息
CacheStats stats = caffeineCache.stats();
System.out.println("命中率: " + stats.hitRate());
System.out.println("加载次数: " + stats.loadCount());
System.out.println("淘汰总数: " + stats.evictionCount());
System.out.println("平均加载耗时(纳秒): " + stats.averageLoadPenalty());
生产监控建议: 将 Caffeine 的统计指标暴露到 Prometheus 或 Micrometer 中,配置 Grafana 监控面板。重点关注 命中率(hitRate) 和 淘汰数(evictionCount)。当命中率低于 80% 或淘汰数突增时,说明缓存容量或过期策略需要调整。

4.5 两级缓存架构(Caffeine + Redis)

在 Redis 热点削峰项目中,Caffeine 通常与 Redis 配合组成两级缓存。以下是推荐的数据读取流程:

  1. 查询 Caffeine: 如果命中,直接返回,延迟 < 1ms。
  2. 查询 Redis: 如果 Caffeine 未命中,查询 Redis,延迟约 1~5ms。
  3. 回源数据库: 如果 Redis 也未命中,查询数据库,延迟约 10~100ms。
  4. 回填缓存: 将数据库结果依次写入 Caffeine 和 Redis。
public Product getProductWithMultiLevelCache(Long productId) {
    // 1. 查 Caffeine
    Product product = caffeineCache.getIfPresent(productId);
    if (product != null) {
        return product;
    }
    // 2. 查 Redis
    String redisKey = "product:" + productId;
    product = redisTemplate.opsForValue().get(redisKey);
    if (product != null) {
        // 回填 Caffeine
        caffeineCache.put(productId, product);
        return product;
    }
    // 3. 查数据库
    product = productRepository.findById(productId).orElse(null);
    if (product != null) {
        // 4. 回填两级缓存
        caffeineCache.put(productId, product);
        redisTemplate.opsForValue().set(redisKey, product, 300, TimeUnit.SECONDS);
    }
    return product;
}
削峰效果: 在秒杀或热点活动中,Caffeine 能够承载 80% 以上的重复热点查询,将 Redis 的 QPS 降低 5~10 倍,有效避免 Redis 连接池耗尽和响应超时。