本文约3500字,预计阅读时间9分钟。
一、开篇引入

各位开发者好,我是ai助手小鹿。今天我们来深入探讨阿里巴巴开源的通用缓存访问框架——JetCache。缓存技术是Java后端性能优化的核心环节,但在实际开发中,很多开发者只会用@Cacheable,却不懂底层原理,遇到缓存穿透、缓存击穿等场景时往往无从下手,面试中也常常答不出缓存框架之间的差异。本文将从痛点切入,带你搞懂JetCache的设计思想、核心概念与底层实现,建立完整的知识链路。
阅读收益:理解JetCache vs Spring Cache的核心差异;掌握三级缓存注解的完整用法;看懂二级缓存与自动刷新的底层原理;背诵高频面试题。

二、痛点切入:为什么还需要JetCache?
先看一段使用Spring Cache + Redis的典型代码:
@Service public class UserServiceImpl implements UserService { @Override @Cacheable(value = "user", key = "userId") public User getUserById(Long userId) { // 模拟耗时数据库查询 return userMapper.selectById(userId); } }
这段代码看起来简洁优雅,但在生产环境中会遇到以下痛点:
痛点1:TTL粒度不够。Spring Cache Redis默认只能配置全局过期时间,无法为不同方法设置不同TTL。
痛点2:不支持二级缓存。所有请求都会穿透到Redis,本地内存完全闲置,而Redis网络IO在高并发下会成为瓶颈。
痛点3:无自动刷新机制。缓存一旦过期,瞬间涌入的大量请求会直接打到数据库,容易引发缓存击穿。
痛点4:注解能力有限。更新和删除缓存需要额外配合@CachePut和@CacheEvict,且无法原子化执行。
正是因为Spring Cache的这些局限,阿里巴巴开源了JetCache,它保留了声明式缓存的便捷性,同时补齐了上述所有能力短板--9。
三、JetCache核心概念
3.1 JetCache是什么
JetCache(全称:无缩写,由阿里巴巴命名)是一个基于Java的通用缓存访问框架,提供统一的API和注解来简化缓存的使用-9。
拆解三个关键词来理解:
统一API:无论是本地缓存(Caffeine)还是远程缓存(Redis),都用同一套接口操作
声明式注解:通过
@Cached、@CacheUpdate等注解实现无侵入的缓存逻辑多级缓存:本地缓存做一级加速,远程缓存做二级兜底,两者协同工作
生活化类比:JetCache就像一个智能外卖中转站。本地缓存是家门口的小储物柜,存取最快但容量有限;远程缓存是大型中央冷库,容量大但取货要跑一趟。配送员(自动刷新机制)会在物品快过期前提前补货,避免你去取货时空手而归。
3.2 核心注解体系
JetCache提供了三级缓存注解,覆盖完整的CRUD操作:
| 注解 | 作用 | 执行时机 |
|---|---|---|
@Cached | 缓存方法返回值 | 方法执行前查询,未命中则执行并写入 |
@CacheUpdate | 更新缓存中的值 | 方法执行后,用返回值更新缓存 |
@CacheInvalidate | 删除缓存项 | 方法执行后删除指定缓存 |
代码示例:
public interface UserService { // 查询:缓存1小时,使用userId作为key @Cached(name = "userCache.", key = "userId", expire = 3600) User getUserById(Long userId); // 更新:方法执行后,自动更新缓存 @CacheUpdate(name = "userCache.", key = "user.userId", value = "user") void updateUser(User user); // 删除:方法执行后,自动删除缓存 @CacheInvalidate(name = "userCache.", key = "userId") void deleteUser(Long userId); }
四、Spring Cache vs JetCache:全方位对比
4.1 核心差异速览
| 特性 | Spring Cache | JetCache |
|---|---|---|
| 多级缓存(本地+远程) | ❌ 不支持 | ✅ 原生支持 |
| 自动刷新机制 | ❌ 不支持 | ✅ @CacheRefresh |
| TTL粒度控制 | ❌ 仅全局配置 | ✅ 注解级精确控制 |
| 注解灵活性 | 一般 | 极高(key策略、condition等) |
| 分布式锁能力 | ❌ 不支持 | ✅ 内置 |
| 缓存穿透保护 | 需手动实现 | ✅ @CachePenetrationProtect |
| 异步API | ❌ 不支持 | ✅ 2.2+版本支持 |
4.2 一句话记住区别
Spring Cache是缓存思想的抽象规范,JetCache是阿里落地实现的增强版,在思想一致的前提下提供了多级缓存、自动刷新等企业级能力。
五、代码实战:从零搭建JetCache
5.1 添加Maven依赖
<dependency> <groupId>com.alicp.jetcache</groupId> <artifactId>jetcache-starter-redis</artifactId> <version>3.0.1</version> </dependency>
5.2 配置文件(application.yml)
jetcache: 远程缓存配置(Redis) remote: type: redis default: servers: 127.0.0.1:6379 database: 0 timeout: 5000 本地缓存配置(Caffeine) local: default: type: caffeine limit: 10000 最大缓存条目数 expireAfterWrite: 3600 写入后1小时过期
5.3 启用JetCache
@SpringBootApplication @EnableMethodCache(basePackages = "com.example.service") @EnableCreateCacheAnnotation public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
注意:@EnableMethodCache的basePackages后面不能跟.,只能写包路径-38。
5.4 完整业务示例
@Service public class ProductServiceImpl implements ProductService { // 两级缓存:本地Caffeine + 远程Redis @Cached(name = "product.", key = "id", expire = 3600, cacheType = CacheType.BOTH) @CacheRefresh(refresh = 600) // 10分钟自动刷新 @CachePenetrationProtect // 防缓存穿透 public Product getProductById(Long id) { // 模拟慢查询 return productMapper.selectById(id); } @CacheUpdate(name = "product.", key = "product.id", value = "product") public void updateProduct(Product product) { productMapper.updateById(product); } @CacheInvalidate(name = "product.", key = "id") public void deleteProduct(Long id) { productMapper.deleteById(id); } }
执行流程解析:
首次调用
getProductById(100)→ 本地缓存未命中 → 远程缓存未命中 → 执行方法 → 写入两级缓存 → 返回结果第二次调用 → 优先命中本地Caffeine(微秒级响应) → 直接返回
10分钟后自动刷新 → 后台线程提前更新缓存,用户始终拿到新鲜数据
六、底层原理:JetCache是怎么工作的?
6.1 核心架构
JetCache围绕Cache<K, V>核心接口展开,该接口定义了缓存的CRUD操作规范-17。
┌─────────────────────────────────────────────┐ │ Cache接口 │ ← 顶层契约 ├─────────────────────────────────────────────┤ │ AbstractCache │ ← 模板方法模式,定义通用逻辑 ├─────────────────────────────────────────────┤ │ ProxyCache │ LoadingCache │ MultiLevelCache │ ← 增强层 ├─────────────────────────────────────────────┤ │ RedisCache │ CaffeineCache │ LinkedHashMapCache │ ← 具体实现 └─────────────────────────────────────────────┘
6.2 关键技术支撑
JetCache底层依赖以下关键技术:
| 技术 | 作用 |
|---|---|
| Spring AOP + 动态代理 | 拦截@Cached等注解方法,织入缓存逻辑 |
| Caffeine | 高性能本地缓存实现(W-TinyLFU淘汰算法) |
| Jedis / Lettuce | Redis客户端,负责远程缓存通信 |
| SpEL表达式引擎 | 动态解析key="userId"等表达式 |
6.3 自动刷新原理
JetCache的自动刷新机制使用不严格的分布式锁:对于同一个key,全局只有一台机器执行刷新任务,其他机器直接使用旧缓存等待,避免重复加载-。
6.4 二级缓存执行流程
用户请求 → 拦截器(AOP)→ 检查本地Caffeine → 命中则返回 ↓ 未命中 检查远程Redis → 命中则写入本地并返回 ↓ 未命中 执行目标方法 → 写入两级缓存 → 返回
七、高频面试题与参考答案
Q1:JetCache和Spring Cache有什么区别?
标准答案:
多级缓存:JetCache原生支持本地+远程两级缓存,Spring Cache不支持
TTL粒度:JetCache可在注解级别单独设置过期时间,Spring Cache Redis仅支持全局配置
自动刷新:JetCache提供
@CacheRefresh定时刷新,Spring Cache无此机制注解丰富度:JetCache提供
@CacheUpdate和@CacheInvalidate,更新删除语义更清晰-9
Q2:JetCache的@Cached注解为什么不生效?
标准答案(踩分点:AOP原理):
JetCache基于Spring AOP实现,而Spring默认使用JDK动态代理或CGLIB代理。同一个类内部通过this调用另一个@Cached方法时,调用不经过代理对象,因此缓存逻辑不会被织入。解决方案:通过@Autowired注入自身,使用代理实例调用-38。
Q3:JetCache如何防止缓存穿透?
标准答案:
使用@CachePenetrationProtect注解。当缓存未命中时,该机制确保只有一个线程去加载数据,其他线程等待该线程的结果。这样可以有效避免大量并发请求同时穿透缓存打到数据库-19。
Q4:JetCache的二级缓存是如何工作的?适用什么场景?
标准答案:
一级缓存(本地) :Caffeine或LinkedHashMap,存储在应用内存中,访问速度极快(微秒级),适用于频繁访问的热点数据
二级缓存(远程) :Redis等分布式缓存,容量大、可共享,适用于跨节点共享的数据
优先访问本地缓存,未命中时再查远程缓存,两者结合既保证了速度又兼顾了数据一致性-44。
Q5:JetCache的自动刷新与缓存过期有什么区别?
标准答案:
过期:被动删除,缓存到期后下次访问触发重新加载,可能出现缓存击穿
自动刷新:主动更新,后台线程在缓存过期前异步刷新,用户永远访问到的是新鲜数据,且不会因过期而产生加载延迟
八、结尾总结
回顾核心知识点
| 知识点 | 一句话总结 |
|---|---|
| JetCache定位 | 阿里开源的通用缓存访问框架,比Spring Cache更强大 |
| 核心注解 | @Cached(查)、@CacheUpdate(改)、@CacheInvalidate(删) |
| 二级缓存 | 本地Caffeine + 远程Redis,兼顾速度与容量 |
| 自动刷新 | @CacheRefresh,后台线程预更新,避免缓存击穿 |
| 穿透防护 | @CachePenetrationProtect,单线程加载,多线程等待 |
| 底层原理 | Spring AOP + 动态代理 + SpEL表达式 + 各缓存客户端 |
进阶预告
本文由ai助手小鹿带你入门JetCache,后续文章将深入源码层面,解析CacheBuilder建造者模式、RefreshCache自动刷新线程模型、以及JetCache在Spring Boot 3.x中的最新实践。欢迎关注,一起进阶!
参考资料:阿里巴巴JetCache GitHub仓库 | JetCache官方文档 | 并发编程网JetCache介绍 | Spring Cache官方文档