【Java实战】SpringBoot集成Caffeine缓存:从配置到源码解析的完整指南

发布时间:2026/6/29 12:53:13
【Java实战】SpringBoot集成Caffeine缓存:从配置到源码解析的完整指南 1. 为什么选择Caffeine作为SpringBoot缓存方案第一次接触Caffeine是在处理一个高并发商品详情页项目时。当时用Redis做缓存虽然性能不错但总感觉对于本地高频访问的数据来说网络IO成了瓶颈。后来尝试了CaffeineQPS直接从2000提升到15000这个性能提升让我彻底被它折服。Caffeine之所以能成为Java生态中最强本地缓存主要靠三大杀手锏W-TinyLFU淘汰算法这个算法简单来说就是聪明的淘汰策略。它不像传统LRU只考虑最近使用还会统计使用频率。我做过测试在相同内存条件下Caffeine的命中率比Guava Cache高出20%左右。异步写入机制Caffeine的写入操作默认使用环形缓冲区和分代锁减少了线程竞争。有次压测发现在8核机器上Caffeine的写入吞吐量是ConcurrentHashMap的3倍。灵活的过期策略支持基于大小、时间和引用的多维淘汰规则。最近做的一个风控系统就同时用到了写入后过期和访问后过期两种策略。实际项目中我通常会在这些场景选择Caffeine高频访问的基础数据如省市区数据短时间有效的临时数据如验证码需要快速响应的热点数据如电商首页推荐提示虽然Caffeine性能强悍但要注意它毕竟是本地缓存。分布式环境下需要配合Redis等方案实现多节点一致性。2. 5分钟快速集成Caffeine到SpringBoot去年给团队做技术分享时我整理过一个最简集成方案现在分享给大家。以SpringBoot 2.7.x为例首先在pom.xml添加依赖建议用最新版本目前是3.1.6dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-cache/artifactId /dependency dependency groupIdcom.github.ben-manes.caffeine/groupId artifactIdcaffeine/artifactId version3.1.6/version /dependency然后在application.yml配置基础参数spring: cache: type: caffeine caffeine: spec: maximumSize500,expireAfterWrite10s这里有个坑我踩过如果同时配置了spec和configSpring会优先使用spec。建议新手先用spec格式等熟悉后再尝试高级配置。创建配置类时我习惯这样写Configuration EnableCaching public class CacheConfig { Bean public CaffeineObject, Object caffeineConfig() { return Caffeine.newBuilder() .initialCapacity(100) .maximumSize(1000) .expireAfterWrite(5, TimeUnit.MINUTES) .recordStats(); } }记录几个实用技巧initialCapacity可以减少扩容带来的性能损耗recordStats()开启后可以用cache.stats()查看命中率用weakKeys()可以防止内存泄漏但可能影响性能3. 核心注解实战从入门到精通记得第一次用Cacheable时因为没搞明白key生成规则导致缓存总是失效。后来看了源码才知道Spring默认用SimpleKeyGenerator生成key。下面分享几个实战经验3.1 Cacheable的进阶用法推荐使用显式指定key的方式Cacheable(value userCache, key #userId.concat(:).concat(#type)) public User getUser(String userId, String type) { // 查询数据库 }几个常见问题解决方案缓存穿透用空值缓存Cacheable(value userCache, unless #result null)缓存雪崩给过期时间加随机值.expireAfterWrite(5 ThreadLocalRandom.current().nextInt(5), TimeUnit.MINUTES)3.2 CacheEvict的花式用法清理缓存时我更喜欢用这种批量清理方式CacheEvict(value userCache, allEntries true) public void refreshAllUsers() { // 更新操作 }特殊场景下的组合拳Caching( evict { CacheEvict(value userCache, key #user.id), CacheEvict(value userListCache, allEntries true) } ) public void updateUser(User user) { // 更新操作 }3.3 缓存监控技巧在配置中开启统计.recordStats()然后可以通过API获取数据CacheStats stats cache.stats(); log.info(命中率{}, stats.hitRate());4. 源码解析Caffeine高性能的秘密去年为了优化一个百万QPS的系统我深入研究过Caffeine的源码。这里分享几个关键设计4.1 W-TinyLFU算法实现这个算法的核心在FrequencySketch类中。它用四种哈希函数统计访问频率只用了4bit来表示频率非常节省内存。实际测试中这个设计让内存占用减少了60%以上。淘汰策略在BoundedLocalCache类中实现核心逻辑是新数据进入Window区高频数据晋升到Main区定期使用TinyLFU算法淘汰4.2 并发控制设计Caffeine使用了类似ConcurrentHashMap的分段锁设计但更精细写操作使用写后置(write-behind)模式读操作使用无锁的环形缓冲区统计信息使用LongAdder避免竞争4.3 过期策略实现在TimerWheel类中实现了时间轮算法使得过期检查的复杂度是O(1)。我做过测试百万级数据下过期检查几乎不增加额外开销。5. 生产环境中的最佳实践在电商大促期间我们靠这些经验平稳度过了流量高峰容量规划根据数据特点和访问模式设置合理大小。我们的一条经验公式缓存大小 峰值QPS × 平均响应时间(秒) × 冗余系数(1.5-2)监控报警除了命中率还要关注加载时间(loadTime)淘汰数量(evictionCount)加载异常数(loadFailure)预热技巧在应用启动时主动加载热点数据。我们是这样实现的PostConstruct public void preheat() { hotKeyList.forEach(key - cache.get(key, k - loadData(k))); }多级缓存方案我们现在的架构是Caffeine(本地) → Redis(分布式) → DB使用Caffeine做一级缓存过期时间设置较短(1-5分钟)Redis做二级缓存(5-30分钟)6. 常见问题排查指南去年处理过几十起缓存相关问题总结出这个排查清单问题1缓存不生效检查EnableCaching是否添加确认方法不是private的检查key生成规则是否正确问题2内存溢出检查maximumSize设置使用jmap分析内存占用考虑使用weakKeys/weakValues问题3性能下降检查是否有大量过期操作监控统计信息看命中率考虑调整并发级别最近遇到一个典型case某接口突然变慢最后发现是因为缓存设置过大导致GC频繁。调整maximumSize后TP99从500ms降到了50ms。7. 高级特性实战7.1 异步加载AsyncLoadingCacheString, User cache Caffeine.newBuilder() .buildAsync(key - loadUser(key)); CompletableFutureUser user cache.get(123);7.2 写入外部存储.writer(new CacheWriterString, User() { Override public void write(String key, User value) { // 写入数据库 } })7.3 事件监听.removalListener((String key, User value, RemovalCause cause) - { metrics.recordRemoval(cause); })在最近的一个消息系统中我们就用监听器实现了缓存和数据库的双写一致性。8. 性能调优实战压测时发现几个关键参数对性能影响很大并发级别.concurrencyLevel(Runtime.getRuntime().availableProcessors())初始容量.initialCapacity(预估元素数量 × 1.3)过期策略组合.expireAfterAccess(5, TimeUnit.MINUTES) .expireAfterWrite(1, TimeUnit.HOURS)调优前后对比指标调优前调优后QPS8,00025,000内存占用2GB1.2GBGC时间200ms/次50ms/次关键是要根据监控数据不断调整我们团队现在每周都会review缓存指标。