mcpField
C
caffeine-patterns

caffeine-patterns

github.com/full-stack-skills开发781 次安装5 周前更新
代码仓库

关于 caffeine-patterns

面向 Java 开发者的 Caffeine 本地缓存模式指南,帮助选择过期、刷新、加载和驱逐策略,并说明 Spring Cache 集成。它还整理了常见误用及进程内缓存的限制,适用于规划单实例缓存行为。

主要功能

  1. 01比较本地缓存与远程缓存场景
  2. 02配置过期和异步刷新策略
  3. 03使用 CacheLoader 加载与重载
  4. 04集成 Spring Cache
  5. 05设置容量驱逐与命中统计
  6. 06说明缓存限制及常见陷阱

适用场景

  1. 01为高频单服务数据选择本地缓存
  2. 02配置过期前后台刷新
  3. 03排查缓存无界增长或加载异常

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/full-stack-skills/java-skills --skill caffeine-patterns

安装后在 Claude Code 中输入/caffeine-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
examples/
caffeine-realworld.md
references/
caffeine-resources.md

详细介绍

来自项目 README

Caffeine JVM 本地缓存

来源:https://github.com/ben-manes/caffeine/wiki
GitHub:https://github.com/ben-manes/caffeine

Capability Boundaries

✅ Strong Suits
  1. 本地 vs 远程选择 — 单服务高频数据用Caffeine,多服务共享用Redis
  2. 过期策略 — expireAfterWrite(写入后) vs expireAfterAccess(访问后) vs refreshAfterWrite(异步刷新)
  3. CacheLoader — 自动加载缺失数据 + reload 异步刷新 + 优雅降级(返回旧值)
  4. Spring Cache 集成 — @Cacheable/@CacheEvict + CaffeineCacheManager
  5. 驱逐策略 — maximumSize(数量)/maximumWeight(权重)/Window TinyLFU
  6. 记录统计 — recordStats() + CacheStats 监控命中率
❌ Out of Scope
  1. 分布式缓存/共享数据 → redis-redisson-patterns
  2. Redis + Caffeine 两级缓存 → redis-redisson-patterns(已覆盖)

过期策略选择决策

策略行为适用场景
expireAfterWrite写入后固定时间过期✅ 推荐(大多数业务),可预测的数据刷新
expireAfterAccess最后访问后固定时间过期会话缓存/用户登录状态
refreshAfterWrite写后固定时间异步刷新(返回旧值)热点数据免阻塞刷新
expireAfterWrite + refreshAfterWrite刷新在过期前执行,永不阻塞⭐ 推荐组合

⚠️ refreshAfterWrite 必须 < expireAfterWrite,否则刷新永远不触发

LLM最常犯的错误

#错误正确做法
1ConcurrentHashMap<String, Object> + 手动过期Caffeine CacheBuilder 窗口 TinyLFU 算法
2所有缓存都走 Redis单服务高频缓存走 Caffeine(纳秒级,Redis 毫秒级)
3不设置 maximumSizemaximumSize(10000) 防止内存溢出
4refreshAfterWrite 设置后不处理加载异常旧值仍可用,加载失败不影响服务
5refreshAfterWrite > expireAfterWrite永远不刷新(过期就直接删除了)
6CacheLoader.load() 返回 null返回 null = 缓存穿透(每次查都走 load)
7只设置 refreshAfterWrite 不设 maximumSize无界缓存 → OOM
8修改缓存返回的对象缓存对象被外部修改 → 数据不一致

核心模式

模式 1: Caffeine 常用配置(推荐组合)
// ✅ expireAfterWrite + refreshAfterWrite 组合(推荐)
Cache<String, User> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(5, TimeUnit.MINUTES)   // 5分钟过期
    .refreshAfterWrite(3, TimeUnit.MINUTES)  // 3分钟后台刷新(返回旧值)
    .recordStats()                            // 记录命中率
    .build(key -> loadFromDB(key));           // CacheLoader
模式 2: CacheLoader + reload 优雅降级
LoadingCache<String, User> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .refreshAfterWrite(3, TimeUnit.MINUTES)
    .build(new CacheLoader<String, User>() {
        @Override
        public @Nullable User load(String key) {
            // 缓存未命中时加载
            User user = userMapper.selectById(Long.parseLong(key));
            // ✅ 不要返回 null!使用 Optional 或空对象占位
            return user != null ? user : NULL_USER;
        }

        @Override
        public @Nullable User reload(String key, User oldValue) {
            // ✅ 刷新时优雅降级:加载失败返回旧值
            try {
                return load(key);
            } catch (Exception e) {
                log.error("刷新缓存失败, key={}", key, e);
                return oldValue;  // ← 返回旧值,不影响服务
            }
        }
    });

// 使用
User user = cache.get("user:1");  // 自动加载
模式 3: Spring Cache 集成
@Configuration
@EnableCaching  // ← 必须!
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager mgr = new CaffeineCacheManager();
        mgr.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(10_000)
            .recordStats());
        return mgr;
    }
}

@Service
public class UserService {
    @Cacheable(value = "users", key = "#id", unless = "#result == null")
    public User getUser(Long id) {
        return userMapper.selectById(id);
    }

    @CacheEvict(value = "users", key = "#id")
    public void updateUser(Long id, UserDTO dto) {
        userMapper.updateById(dto.toEntity(id));
    }

    @CacheEvict(value = "users", allEntries = true)  // 清空整个缓存
    public void refreshAll() { }
}
模式 4: 异步加载(AsyncCache)
AsyncCache<String, User> asyncCache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .refreshAfterWrite(1, TimeUnit.MINUTES)  // 异步刷新
    .buildAsync(key -> loadFromDB(key));

// 异步获取
CompletableFuture<User> future = asyncCache.get("user:1");
future.thenAccept(user -> System.out.println(user));
模式 5: 手动缓存操作 + 统计
Cache<String, User> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .recordStats()  // 启用统计
    .build();

// 手动写入
cache.put("user:1", user);
// 手动失效
cache.invalidate("user:1");

// 统计数据
CacheStats stats = cache.stats();
log.info("命中率: {}, 加载次数: {}, 驱逐次数: {}",
    stats.hitRate(), stats.loadCount(), stats.evictionCount());

Gotchas

  1. Caffeine 是进程内缓存 — 多实例部署每个实例有独立缓存,数据不一致
  2. expireAfterAccess 适合会话缓存 — 写入后频率很高就设置 access
  3. refreshAfterWrite 必须 < expireAfterWrite — 否则刷新没意义
  4. CacheLoader.load() 不能返回 null — 返回空占位对象或抛异常
  5. Spring Cache 的 @Cacheable 返回值必须可序列化 — 如果用 Redis 作为后端
  6. maximumSize 驱逐可能丢失数据 — 重要数据用数据库作为最终保障
  7. 与 Redisson 配合 — Caffeine(L1) + Redis(L2) 是经典的两级缓存组合
  8. 缓存对象可变时注意防御性拷贝 — 外部修改缓存对象导致数据不一致
  9. recordStats() 不影响性能 — 生产环境建议开启
  10. expireAfter(Expiry) 支持自定义过期时间 — 根据 value 动态决定 TTL
  11. Caffeine 3.x 与 2.x 兼容性 — Spring Boot 2.x 使用 Caffeine 2.x,Spring Boot 3.x 使用 3.x
  12. weakKeys/softValues 慎用 — GC 触发的驱逐不可预测

Data Privacy

本技能不收集、存储或传输任何用户数据。

评论

支持 Markdown