Caffeine LoadingCache 最佳实践

529 阅读3分钟

Caffeine 是一个高性能的 Java 缓存库,其 LoadingCache 提供了自动加载和缓存管理功能。以下是使用 Caffeine LoadingCache 时的一些建议:


1. 合理配置缓存参数

  • 初始容量:通过 initialCapacity 设置合理的初始容量,避免频繁扩容。
  • 最大容量:使用 maximumSizemaximumWeight 限制缓存大小,防止内存溢出。
  • 过期策略
    • 时间过期expireAfterWrite(写入后过期)或 expireAfterAccess(访问后过期)。
    • 权重策略:对缓存项自定义权重(通过 weigher),适用于不同大小的缓存对象。
LoadingCache<Key, Value> cache = Caffeine.newBuilder()
    .initialCapacity(100)
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(key -> loadDataFromDB(key));

2. 避免缓存击穿与雪崩

  • 同步加载:使用 LoadingCacheget 方法自动加载数据,天然避免击穿(同一 Key 并发请求只触发一次加载)。
  • 异步加载:通过 refreshAfterWrite 设置定时刷新,结合 AsyncLoadingCache 异步加载,减少阻塞。
  • 随机过期时间:对过期时间添加随机偏移(如 ±10%),避免大量缓存同时失效(雪崩)。
AsyncLoadingCache<Key, Value> asyncCache = Caffeine.newBuilder()
    .refreshAfterWrite(5, TimeUnit.MINUTES)
    .buildAsync(key -> loadDataAsync(key));

3. 缓存预热

  • 启动时预加载:在系统初始化阶段主动加载热点数据,避免首次请求延迟。
  • 批量加载:使用 getAll 方法批量加载数据,减少多次 IO 开销。
// 预热单个 Key
cache.get(hotKey);

// 预热多个 Key
cache.getAll(hotKeys);

4. 处理缓存穿透

  • 空值占位:对不存在的 Key 缓存空值(如 Optional.empty()),避免频繁查询底层数据源。
  • 布隆过滤器:结合布隆过滤器(Bloom Filter)快速判断 Key 是否存在。
LoadingCache<Key, Optional<Value>> cache = Caffeine.newBuilder()
    .build(key -> {
        Value value = loadDataFromDB(key);
        return Optional.ofNullable(value);
    });

5. 异常处理与降级

  • 捕获加载异常:在 CacheLoader 中处理异常,避免缓存污染。
  • 降级策略:返回默认值或旧数据,保证系统可用性。
LoadingCache<Key, Value> cache = Caffeine.newBuilder()
    .build(new CacheLoader<Key, Value>() {
        @Override
        public Value load(Key key) {
            try {
                return loadDataFromDB(key);
            } catch (Exception e) {
                return getDefaultValue();
            }
        }
    });

6. 监控与调优

  • 开启统计:通过 recordStats() 收集命中率、加载时间等指标。
  • 动态调整:根据监控数据优化参数(如最大容量、过期时间)。
LoadingCache<Key, Value> cache = Caffeine.newBuilder()
    .recordStats()
    .build(key -> loadData(key));

// 获取统计信息
CacheStats stats = cache.stats();
double hitRate = stats.hitRate();

7. 结合读写策略

  • Write-Through:通过 CacheWriter 实现缓存与数据源的同步更新。
  • Write-Behind:异步批量更新数据源(需结合外部队列或线程池)。
CacheWriter<Key, Value> writer = new CacheWriter<>() {
    @Override
    public void write(Key key, Value value) {
        // 同步写入数据库
        saveToDB(key, value);
    }
};

LoadingCache<Key, Value> cache = Caffeine.newBuilder()
    .writer(writer)
    .build(key -> loadData(key));

8. 线程安全与资源释放

  • 线程安全:Caffeine 缓存本身是线程安全的,但 CacheLoader 的实现需保证幂等性。
  • 关闭缓存:在应用关闭时调用 cache.cleanUp()cache.invalidateAll() 释放资源。

适用场景建议

  • 高频读、低频写:适合缓存静态或半静态数据(如配置信息)。
  • 计算成本高:缓存复杂计算结果(如机器学习模型输出)。
  • 保护底层系统:减少对数据库或 API 的频繁调用。

以上配置可以显著提升缓存命中率、降低延迟,同时避免常见问题(击穿、雪崩、穿透)。建议结合具体业务场景调整参数,并通过压力测试验证效果。