Caffeine原理
ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花
Caffeine是Java生态中性能顶尖的本地缓存库,对标Google Guava Cache并实现全面超越,主打高吞吐量、低延迟、超高缓存命中率,核心优势源于创新的淘汰算法、精细化并发设计和高效内存管控。它完美解决了传统LRU缓存污染、LFU内存开销大、新热点数据难留存的痛点,成为分布式系统、本地缓存场景的首选方案。
一、底层存储架构:高效并发+低开销统计
Caffeine没有重新造轮子,而是基于Java原生并发容器优化,搭配专用数据结构实现读写分离、无锁争抢,核心存储组件分为三部分:
- ConcurrentHashMap(主存储):作为缓存键值对的核心载体,沿用JDK分段锁+CAS机制,保证多线程读写的基础线程安全;同时优化了哈希冲突、扩容逻辑,避免传统ConcurrentHashMap的性能瓶颈,支撑百万级QPS的读写请求。
- 环形缓冲区(Ring Buffer):用于异步记录缓存访问、写入、淘汰等事件,将热点操作异步化处理,避免主线程阻塞,大幅降低锁争用概率,实现读写操作的解耦。
- Count-Min Sketch(频率素描):轻量化概率数据结构,仅用极小内存(相比传统LFU节省90%以上)统计缓存条目的访问频率,解决传统LFU频率字段内存开销大、频率更新滞后的问题,为淘汰算法提供精准依据。
核心设计思路:存储与管控分离,主存负责数据读写,辅助结构负责频率统计、事件调度,兼顾并发性能和内存效率。
二、核心淘汰算法:W-TinyLFU(Caffeine灵魂)
W-TinyLFU(Window TinyLFU)是Caffeine碾压传统缓存的核心,它融合LRU(最近最少使用)和LFU(最不经常使用)的优势,通过窗口分区+多级缓存机制,彻底解决缓存污染、新热点被挤出的问题,整体架构分为两大区域:
1. 窗口缓存区(Window Cache)
占用总容量约1%的极小区域,专门接纳新写入/新访问的缓存条目。新数据不会直接进入主缓存,而是先在窗口区短暂留存,既避免突发冷数据(如爬虫遍历、批量查询)污染主缓存,又能保护刚上线的新热点数据(如新品、突发热点),防止其被长期冷数据直接淘汰。
窗口区采用LRU策略管理,满额时会将淘汰的候选数据推入主缓存区,参与主缓存的筛选淘汰。
2. 主缓存区(Main Cache)
占用总容量99%的核心区域,采用SLRU(分段LRU)架构,细分为保护区和 probation区,结合Count-Min Sketch的频率统计筛选热点数据:
- Probation区(试探区):接收窗口区淘汰的候选数据,属于“待观察”区域,采用LRU策略管理;当该区满额时,会对比候选数据和区内数据的访问频率,频率低的直接被淘汰。
- Protected区(保护区):存储高频热点数据,容量占主缓存的80%左右,只有Probation区内访问频率达标的数据才能晋升至此,被重点保护;只有保护区满额时,才会将低频数据降级回Probation区。
3. 完整淘汰逻辑
- 新数据先写入窗口缓存区,窗口区满额则按LRU淘汰候选数据至主缓存试探区;
- 试探区数据通过访问频率筛选,高频数据晋升保护区,低频数据直接剔除;
- 主缓存满额时,结合Count-Min Sketch的频率统计,优先淘汰频率最低+最近未访问的数据;
- 定期衰减频率计数,避免历史高频数据长期霸占缓存,保证新热点有晋升空间。
三、并发控制:无锁读写+伪共享优化
Caffeine针对高并发场景做了极致优化,摒弃粗粒度锁,实现读写无争抢、扩容无阻塞:
- 细粒度分段锁:仅对缓存桶级别的数据加锁,不同桶的读写操作完全并行,锁冲突概率极低;
- 无锁读写设计:读操作全程无锁,写操作仅锁定对应数据段,配合环形缓冲区异步处理淘汰、过期事件,主线程几乎无等待;
- 伪共享优化:通过内存对齐技术,将并发访问的核心变量分隔到不同CPU缓存行,避免多核CPU之间的缓存一致性流量损耗,进一步提升并发性能。
四、过期与清理机制:时间轮+异步懒清理
Caffeine支持按过期时间、写入时间、访问时间管控缓存生命周期,采用时间轮算法实现高效过期管理,避免定时扫描的性能损耗:
- 时间轮调度:用环形时间轮存储过期条目,每个槽位对应一个时间片,条目按过期时间落入对应槽位;时间轮转动时自动清理到期数据,调度效率远高于传统定时任务;
- 懒清理+异步清理:不主动全量扫描缓存,读写时触发懒清理(校验条目是否过期);同时开启异步线程批量清理过期、淘汰数据,不阻塞业务主线程;
- 引用类型支持:支持软引用、弱引用缓存条目,配合JVM垃圾回收,实现内存敏感场景下的自动缓存释放。
五、缓存加载模式:灵活适配业务场景
Caffeine提供三种加载模式,支持同步/异步加载、懒加载、主动预热,适配不同业务需求:
- 手动加载:业务代码主动调用put、get方法读写缓存,可控性最强,适合自定义加载逻辑;
- 自动加载:配置CacheLoader,get时若缓存不存在,自动调用Loader加载源数据并写入缓存,实现懒加载;
- 异步加载:基于CompletableFuture实现异步加载,get时不阻塞主线程,加载完成后自动回填缓存,适合高并发、慢数据源场景;同时支持定时刷新缓存,保证数据时效性。
六、核心优势与适用场景
核心优势
- 命中率远超Guava Cache(LRU)、传统LFU,高并发场景下提升10%-20%;
- 吞吐量、延迟指标碾压同类本地缓存,内存开销极低;
- 功能完备:支持过期、刷新、淘汰、监控、弱引用等全特性;
- 无第三方依赖,接入简单,兼容Java 8+全版本。
适用场景
分布式系统本地缓存、热点数据缓存、接口防重、计算结果缓存、高并发读场景;不适合数据强一致、超大容量缓存(建议单实例缓存容量控制在GB级)。
ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花
本专栏带你从零掌握 Redis 核心知识,清晰讲解过期策略、内存淘汰等面试重点。用通俗语言拆解底层原理,搭配实战案例与常见问题总结,兼顾入门理解与面试备考,帮你快速建立完整 Redis 知识体系,轻松应对开发与面试