北京时间2026年4月10日 | 目标读者:技术入门/进阶学习者、在校学生、面试备考者、开发工程师
定位:技术科普 + 原理讲解 + 代码示例 + 面试要点,兼顾易懂性与实用性
一、开篇引入

在性能优化领域,有一个被反复强调的铁律——缓存是性能优化的第一手段。当QPS从1000涨到10万时,如果不加缓存,数据库就是第一个崩溃的地方-49。但现实中,很多人用缓存的方式是:查Redis → 取不到 → 查数据库 → 写回去,然后就说“我会用缓存了”。
事实上,学会用缓存和真正懂缓存之间,隔着一道巨大的认知鸿沟。许多开发者在使用Spring Cache注解时频频踩坑:@Cacheable加了却不生效、缓存跟数据库对不上、热点数据一过期系统就崩了……这些问题的根源,不在于代码写错了哪一行,而在于没有理解缓存的本质——它不是一个简单的KV存储,而是一套需要从“问题 → 概念 → 关系 → 示例 → 原理”逐层吃透的知识体系。

本文将从 AI工控助手 的视角出发,系统地讲解本地缓存与分布式缓存的核心概念、选型对比、代码实战,以及底层原理与面试高频题,帮你在30分钟内建立完整的缓存知识链路。
二、痛点切入:为什么需要缓存?
传统实现方式:纯数据库查询
// 传统方式:每次请求都查数据库 @Service public class ProductService { @Autowired private ProductRepository productRepository; public Product getProductById(Long id) { // 每次调用都执行SQL查询 return productRepository.findById(id).orElse(null); } }
上面这段代码看起来简洁,但隐藏着巨大的性能问题。一次数据库查询加上连接开销、SQL解析和结果映射,通常需要200-300ms。对于一个日活10万的系统,如果每个请求都穿透到数据库,DB的QPS压力会呈指数级增长-11。
传统方式的四大痛点
耦合度高:业务逻辑和性能瓶颈混在一起,代码难以维护
扩展性差:想加缓存需要大改代码,甚至重构整个数据访问层
维护困难:没有统一的缓存管理入口,排查问题耗时耗力
代码冗余:每个需要缓存的Service都要写一套“先查缓存,再查DB”的样板代码
缓存技术的出现,正是为了解决这些问题。它的核心设计理念是:将频繁访问的热点数据存放在更快的存储介质中,让业务代码和缓存逻辑解耦,通过统一的抽象层管理。
三、核心概念讲解:本地缓存(Caffeine)
标准定义
Caffeine(发音 /ˈkæfiːn/)是一款高性能、可扩展的Java内存缓存库,由Google开发并开源,是Spring Boot 5+默认推荐的本地缓存实现-1。
拆解关键词
“本地” :缓存数据存储在JVM堆内存中,不需要网络请求,读取速度可达纳秒级别
“高性能” :读写吞吐量比Guava Cache提升4倍以上,写入性能高20%-35%-1-2
“内存缓存” :利用RAM作为存储介质,重启即失,适合存储可重建的临时数据
“自动淘汰” :当内存占满时,自动驱逐最不常用的数据,避免OOM
生活化类比
把Caffeine想象成你的随身笔记本:
你每天要查很多次“公司餐厅今天吃什么”(热点数据查询)
与其每次都跑去食堂看公告板(查数据库),不如早上看一眼,记在笔记本里(写入缓存)
笔记本容量有限,记满了就擦掉最不常用的内容(淘汰策略)
随身携带,随翻随用,完全不依赖网络(本地访问)
核心价值
Java本地缓存的核心目标是在不依赖外部缓存服务的前提下,利用JVM堆内存快速存取热点数据,从而显著降低数据库或远程服务的访问压力,提升响应速度与系统吞吐量-。
四、关联概念讲解:分布式缓存(Redis)
标准定义
Redis(Remote Dictionary Server)是一款开源的内存数据结构存储系统,常被用作缓存、数据库和消息代理。它支持String、Hash、List、Set等多种数据结构,数据存储在内存中,可实现亚毫秒级的响应时间-31。
与Caffeine的关系
Caffeine和Redis不是“二选一”的关系,而是互补关系:
| 对比维度 | Caffeine(本地缓存) | Redis(分布式缓存) |
|---|---|---|
| 存储位置 | JVM堆内存 | 独立的缓存服务器内存 |
| 访问延迟 | < 0.1ms | 0.5-2ms(网络RTT) |
| 容量限制 | 受JVM堆大小限制(百MB级) | 可扩展至百GB级 |
| 数据共享 | 单JVM内独享,多实例间不共享 | 跨服务、跨实例共享 |
| 持久化 | 不支持,重启即失 | 支持RDB/AOF持久化 |
| 适用场景 | 单机高频访问、极低延迟要求 | 跨服务共享、大规模数据 |
一句话概括:Caffeine追求极致速度,Redis解决数据共享;两者结合形成多级缓存,才是生产环境的最佳实践-1。
运行机制示例
Caffeine工作流程:请求 → 查本地缓存 → 命中返回(<0.1ms)→ 未命中查DB → 回写本地缓存
Redis工作流程:请求 → 通过网络请求Redis → 命中返回(0.5-2ms)→ 未命中查DB → 回写Redis
五、概念关系与区别总结
把缓存体系理解为“思想 vs 实现”的关系:
缓存思想:用空间换时间,用快速存储承载热点数据
本地缓存:缓存在Java进程内部落地的一种具体实现
分布式缓存:缓存作为独立服务落地的一种具体实现
在实际架构中,Caffeine负责拦截高频读请求,让90%以上的请求在JVM内部就完成,Redis负责在多个服务实例之间共享数据和兜底命中-49。
六、代码/流程示例:Spring Boot集成Caffeine
6.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> </dependency>
6.2 开启缓存并配置Caffeine
@Configuration @EnableCaching // 关键:开启Spring缓存机制 public class CacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager("products", "users"); cacheManager.setCaffeine(Caffeine.newBuilder() .maximumSize(10000) // 最大缓存条目数 .expireAfterWrite(5, TimeUnit.MINUTES) // 写入后5分钟过期 .recordStats() // 开启命中统计 .removalListener((key, value, cause) -> log.info("缓存移除 key={}, cause={}", key, cause)) ); return cacheManager; } }
6.3 使用@Cacheable注解
@Service public class ProductService { // 正确用法:在public方法上使用@Cacheable @Cacheable(value = "products", key = "id") public Product getProductById(Long id) { // 模拟数据库查询(只有缓存未命中时才会执行) System.out.println("--- 正在从数据库查询产品 ID: " + id + " ---"); return productRepository.findById(id).orElse(null); } }
6.4 自调用陷阱:@Cacheable失效的经典案例
@Service public class OrderService { // 错误示例:在类内部调用带@Cacheable的方法 public Order getOrderWithCache(Long id) { // this调用会绕过AOP代理,缓存注解不生效! return this.findOrderById(id); } @Cacheable(value = "orders", key = "id") public Order findOrderById(Long id) { return orderRepository.findById(id).orElse(null); } }
正确做法:将@Cacheable方法抽到独立的Service类中,通过注入调用-69。
@Service public class OrderCacheService { @Cacheable(value = "orders", key = "id") public Order findOrderById(Long id) { return orderRepository.findById(id).orElse(null); } } @Service public class OrderService { private final OrderCacheService orderCacheService; // 注入 public Order getOrderWithCache(Long id) { return orderCacheService.findOrderById(id); // 缓存生效 } }
6.5 执行流程解析
首次调用
getProductById(1001):缓存未命中 → 执行数据库查询 → 将结果写入Caffeine → 返回数据第二次调用相同参数:缓存命中 → 直接从Caffeine返回,数据库查询被“短路”
5分钟后:缓存过期,下次访问重新触发加载
七、底层原理/技术支撑点
7.1 Caffeine的核心算法:W-TinyLFU
Caffeine没有采用传统的LRU(Least Recently Used,最近最少使用)淘汰算法,而是基于W-TinyLFU(Window Tiny Least Frequently Used,窗口最小频繁使用算法)-13。
传统LRU的痛点:LRU容易被偶发热点数据污染缓存——比如突然来一波爬虫请求,把所有缓存条目都挤出去,真正的热点数据反而被淘汰了。
W-TinyLFU的优势:
基于频率统计而非单纯的“最近访问”
使用布隆过滤器 + 频率sketch做热点识别,能过滤掉“一次写入、极少再读”的噪声条目-2
命中率比LRU更高,内存效率更优
7.2 Spring Cache的底层:AOP代理
@Cacheable注解之所以能“短路”方法执行,底层依赖的是Spring AOP(Aspect-Oriented Programming,面向切面编程)-72:
Spring为带有@Cacheable的目标Bean创建AOP代理对象
外部调用时先经过代理 → 代理执行缓存拦截逻辑 → 命中则直接返回
只有未命中时,才调用原始方法并回写缓存
自调用失效的本质就是this调用绕过了代理对象,切面逻辑根本不触发-67。
八、高频面试题与参考答案
面试题1:Caffeine和Guava Cache有什么区别?
参考答案(三个踩分点):
算法不同:Caffeine采用W-TinyLFU淘汰算法,命中率更高;Guava Cache使用传统LRU,容易被热点数据污染-13
性能差异:Caffeine读写吞吐量比Guava提升4倍以上,写入性能高20%-35%-1
生态地位:Spring 5+已默认推荐Caffeine,Guava Cache仅建议旧项目维护使用-1
面试题2:@Cacheable在什么情况下会失效?
参考答案(三个常见场景):
自调用:同一个类中的方法调用this.method()会绕过AOP代理,缓存注解不生效-69
缺少@EnableCaching:主类或配置类上未标注此注解,Spring根本不会处理缓存-69
方法非public:Spring AOP默认只对public方法生效
面试题3:expireAfterWrite和refreshAfterWrite有什么区别?
参考答案:
expireAfterWrite:写入后指定时间过期,过期即删,下次访问时重新加载-2
refreshAfterWrite:写入后指定时间触发刷新,但刷新是异步的,刷新期间仍返回旧值-2
关键区别:expireAfterWrite是“过期就没了”,refreshAfterWrite是“后台刷新,旧值还能用”
面试题4:什么是缓存穿透、击穿、雪崩?怎么解决?
| 问题 | 定义 | 解决方案 |
|---|---|---|
| 穿透 | 请求的数据在缓存和DB都不存在,每次都打DB | 缓存空值、布隆过滤器-39 |
| 击穿 | 热点key失效瞬间,大量请求同时打DB | 加互斥锁、永不过期+异步刷新-39 |
| 雪崩 | 大量key同时过期或Redis宕机 | 随机TTL、预热、集群部署-39 |
面试题5:缓存双写一致性怎么保证?
参考答案:
主流方案:先更新数据库,再删除缓存(Cache-Aside Pattern)
进阶方案:延迟双删(先删缓存 → 更新DB → 延迟一段时间再删一次),解决并发读写导致的旧数据回写问题-63
强一致方案:监听数据库binlog(如Canal),异步同步到缓存-57
九、结尾总结
本文核心知识点回顾
本地缓存(Caffeine) :存储在JVM内存,极低延迟(<0.1ms),适合单机高频访问,基于W-TinyLFU算法
分布式缓存(Redis) :独立服务,跨实例共享数据,适合大规模数据缓存和分布式场景
多级缓存架构:Caffeine + Redis组合,本地缓存挡高频读请求,Redis做兜底和共享
Spring Cache底层原理:基于AOP代理实现,自调用会绕过代理导致缓存失效
三大缓存问题:穿透、击穿、雪崩及其解决方案
双写一致性:先更新DB再删缓存,配合延迟双删或binlog同步
易错点强调
自调用导致@Cacheable不生效,是线上最常见的bug之一
expireAfterWrite和refreshAfterWrite不能混淆使用
本地缓存和分布式缓存不是互斥的,多级才是最佳实践
下篇预告
下一篇我们将深入讲解 Spring事务管理 的底层原理与常见陷阱,从@Transactional失效分析到事务传播行为,再到面试高频题解析,帮你彻底吃透事务这块硬骨头。
AI工控助手小贴士:本文所有代码示例均已简化,适合快速上手。如在实际项目中遇到复杂缓存场景(如多级缓存联动、动态刷新),欢迎在评论区留言讨论。
本文为AI工控助手技术科普系列第3篇,关注我们获取更多硬核技术干货。
扫一扫微信交流