1. 为什么需要多级缓存架构在互联网应用开发中缓存是提升系统性能的利器。但很多开发者容易陷入一个误区要么只用本地缓存要么只用分布式缓存。实际上这两种方式各有优缺点而真正的高性能架构往往需要将它们结合起来使用。本地缓存比如Caffeine的访问速度极快通常能在纳秒级别完成操作。我曾经做过测试Caffeine的读取性能比直接访问内存中的HashMap还要快。但本地缓存有个致命问题它只在单个JVM实例中有效在分布式环境下会出现数据不一致的情况。分布式缓存比如Redis解决了数据一致性问题所有服务实例都能访问同一份数据。但每次网络请求都要付出额外的IO代价实测下来Redis的访问延迟通常在毫秒级别比本地缓存慢了上千倍。JetCache的聪明之处在于它把这两种缓存组合成了一个多级缓存系统。你可以把它想象成计算机的CPU缓存架构L1缓存速度最快但容量小L2缓存稍慢但容量大。JetCache中的本地缓存就是L1Redis就是L2。当请求到达时系统会先查本地缓存查不到再查Redis最后才查数据库。2. JetCache核心功能解析2.1 灵活的缓存类型支持JetCache支持三种缓存模式我在项目中都实际使用过LOCAL模式只使用本地缓存适合配置信息等不常变化的数据REMOTE模式只使用远程缓存适合需要严格一致性的场景BOTH模式同时使用两级缓存这是最常用的模式配置示例jetcache: local: default: type: caffeine keyConvertor: fastjson remote: default: type: redis keyConvertor: fastjson valueEncoder: java valueDecoder: java2.2 注解驱动的开发方式JetCache提供了非常方便的注解API我最常用的是这三个Cached方法结果自动缓存CacheUpdate更新缓存CacheInvalidate删除缓存实际案例GetMapping(/product/{id}) Cached(nameproduct:, key#id, expire3600, cacheTypeCacheType.BOTH) public Product getProduct(PathVariable Long id) { // 数据库查询逻辑 } PostMapping(/product/update) CacheUpdate(nameproduct:, key#product.id, value#product) public void updateProduct(RequestBody Product product) { // 更新数据库 }2.3 缓存一致性保障多级缓存最头疼的就是一致性问题。JetCache提供了几种解决方案通过broadcastChannel配置当某个节点更新缓存时会自动通知其他节点失效本地缓存可以设置syncLocaltrue强制在更新远程缓存时同步更新本地缓存对于特别敏感的数据可以设置本地缓存过期时间很短比如30秒3. Caffeine性能优化实战3.1 缓存淘汰策略选择Caffeine提供了多种淘汰策略经过反复测试我总结出这些经验maximumSize适合缓存对象大小均匀的场景maximumWeight适合缓存对象大小差异大的情况expireAfterWrite适合数据变更频繁的场景expireAfterAccess适合热点数据集中场景生产环境推荐配置CacheString, Object cache Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(30, TimeUnit.MINUTES) .refreshAfterWrite(15, TimeUnit.MINUTES) .recordStats() .build();3.2 缓存预热技巧冷启动时缓存命中率低是个常见问题。我的解决方案是使用LoadingCache自动加载启动时异步预热PostConstruct public void init() { CompletableFuture.runAsync(() - { // 预热热门数据 hotKeys.forEach(key - loadingCache.get(key)); }); }3.3 监控与调优Caffeine的统计功能非常实用CacheStats stats cache.stats(); log.info(命中率{}%加载平均耗时{}ms, stats.hitRate() * 100, stats.averageLoadPenalty() / 1_000_000);根据这些指标可以动态调整缓存策略比如当命中率低于80%时考虑增加缓存容量。4. 生产环境最佳实践4.1 缓存Key设计规范我遇到过不少因为key设计不当导致的问题现在团队里强制要求业务前缀如user:info:版本号如v1:完整定位如user:info:v1:1001避免特殊字符只使用字母数字和冒号4.2 缓存雪崩预防我们曾经因为缓存集中过期导致雪崩现在的防护措施基础过期时间 随机抖动expireAfterWrite(30 ThreadLocalRandom.current().nextInt(10), TimeUnit.MINUTES)二级缓存策略本地缓存过期时间比远程缓存短限流降级使用Hystrix或Resilience4j做保护4.3 大Value处理方案当缓存对象很大时超过1MB需要特殊处理压缩存储valueEncoder: kryo valueDecoder: kryo分片存储将大对象拆分成多个小对象考虑使用本地文件缓存替代5. 常见问题排查指南5.1 缓存穿透防护我们遇到过恶意攻击导致的缓存穿透解决方案空值缓存Cached(cacheNullValue true)Bloom过滤器前置校验接口限流5.2 序列化异常处理跨语言环境下容易出现序列化问题我的经验是统一使用JSON序列化复杂对象实现Serializable字段变更时考虑兼容性5.3 版本升级注意事项JetCache 2.7.x版本有个大坑要注意!-- 必须额外添加 -- dependency groupIdredis.clients/groupId artifactIdjedis/artifactId version4.3.1/version /dependency建议先在测试环境验证我们曾经因为版本问题导致线上缓存全部失效。