Caffeine 作为 Java 领域高性能本地缓存库,其设计目标围绕高吞吐量、低延迟、高效内存管理展开,核心能力可从技术特性与业务价值两个维度拆解:
缓存策略先进性
CacheLoader
异步加载数据,通过 refreshAfterWrite
实现缓存数据后台刷新,避免穿透数据库Weigher
接口实现基于权重的容量控制,支持对象引用类型(弱引用值、软引用键)降低内存压力工程化特性
@Cacheable
/@CacheEvict
注解,支持与 Spring Boot Actuator 监控指标对接Cache.stats()
接口,可获取命中率、加载耗时、淘汰次数等核心指标,支持 debug()
模式打印详细日志核心模块说明
Striped64
)实现高并发访问,热点数据存储于 ConcurrentHashMap
,冷数据通过 LinkedHashSet
维护淘汰顺序expireAfterWrite
(写入后过期)、expireAfterAccess
(访问后过期)maximumSize
(最大条目数)、maximumWeight
(最大权重)Cache.get(key, loader)
AsyncCache.supply(key, supplier)
refreshAfterWrite
触发 CacheLoader.reload
加载流程核心逻辑
// 同步加载示例(LoadingCache)
LoadingCache<String, User> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> db.queryUser(key)); // 自定义加载逻辑
User user = cache.get("user:123"); // 命中缓存直接返回,未命中则调用 loader 加载
异步加载与刷新
// 异步加载示例(AsyncLoadingCache)
AsyncLoadingCache<String, Image> asyncCache = Caffeine.newBuilder()
.executor(Executors.newFixedThreadPool(10)) // 指定异步线程池
.buildAsync(key -> loadImageAsync(key)); // 异步加载函数返回 CompletableFuture
CompletableFuture<Image> future = asyncCache.get("image:456");
future.thenAccept(image -> display(image)); // 异步处理结果
过期策略对比
策略 | 适用场景 | 实现原理 |
---|---|---|
expireAfterWrite |
数据有明确失效时间(如商品价格) | 记录键的最后写入时间,超过阈值则标记为过期 |
expireAfterAccess |
访问频率低的数据(如历史订单) | 记录键的最后访问时间,结合 refreshAfterWrite 实现热点数据自动刷新 |
weakKeys |
键为临时对象(如请求上下文) | 使用 WeakReference 存储键,GC 时自动清理无人引用的键 |
softValues |
大对象缓存(如图片二进制数据) | 使用 SoftReference 存储值,内存不足时由 JVM 自动回收 |
核心原理
参数配置影响
Caffeine.newBuilder()
.initialCapacity(100) // 初始容量,影响分段锁粒度
.concurrencyLevel(4) // 并发级别,控制锁分段数量(建议 CPU 核心数)
.recordStats() // 启用统计功能,采集命中率、加载时间等指标
多实例缓存一致性方案
方案 | 实现方式 | 适用场景 | 延迟 / 吞吐量 |
---|---|---|---|
本地广播 | 通过 Guava EventBus 或 Spring ApplicationEvent 实现实例间缓存失效通知 |
小规模集群(<10 节点) | 低延迟 |
消息队列 | 缓存变更时发布消息(如 Kafka/Redis Pub/Sub),其他实例监听主题并更新缓存 | 中等规模集群 | 秒级延迟 |
分布式锁 | 结合 Redis 分布式锁保证同一时间仅单实例更新缓存,避免缓存击穿 | 写多读少场景 | 高吞吐量 |
性能优化参数示例
Caffeine<String, Object> cache = Caffeine.newBuilder()
// 容量优化
.maximumSize(10_000) // 最大条目数(根据堆内存调整,建议占堆内存 20%-30%)
.weigher((k, v) -> v.getSize()) // 基于对象大小的权重计算
// 过期策略
.expireAfterWrite(5, TimeUnit.MINUTES) // 常用数据短周期过期
.refreshAfterWrite(3, TimeUnit.MINUTES) // 提前 2 分钟刷新热点数据
// 并发优化
.concurrencyLevel(Runtime.getRuntime().availableProcessors()) // 按 CPU 核心数设置并发级别
.build();
典型故障处理流程
场景 1:缓存命中率突然下降(<50%)
Cache.stats().hitRate()
确认命中率骤降maximumSize
扩大缓存容量expireAfterAccess
延长热点数据存活时间cache.invalidateAll()
调用场景 2:缓存加载线程阻塞
现象:应用线程池队列积压,响应延迟升高
诊断工具
jstack
查看线程栈,确认是否有大量线程阻塞在 Cache.get()
启用debug()
模式打印加载耗时日志:
Caffeine.newBuilder().debug().build(); // 输出详细加载日志
优化措施
executor(Executors.newFixedThreadPool(20))
supplyAsync
非阻塞获取分段锁设计
Segment
对象Segment
包含:
count
:段内条目数(原子变量)map
:ConcurrentHashMap
存储键值对queue
:ArrayDeque
维护访问顺序(用于淘汰算法)源码关键片段(Segment.java)
// 写入操作(简化版)
void put(K key, V value, long now) {
map.put(key, value); // 写入 ConcurrentHashMap
recordAccess(key, now); // 更新访问队列
maybeEvict(); // 触发淘汰检查
}
// 淘汰检查
private void maybeEvict() {
if (count.get() > maximumSize) {
evictEntries(1); // 每次淘汰 1 个条目
}
}
频率统计核心类(FrequencySketch)
// 记录键的访问频率(简化版)
class FrequencySketch {
private final int[] counter; // 计数器数组
private final int window; // 时间窗口(秒)
public void recordAccess(K key) {
int hash = key.hashCode() % counter.length;
if (isWithinWindow(key)) {
counter[hash]++; // 同一窗口内访问计数累加
} else {
counter[hash] = 1; // 新窗口重置计数
}
}
private boolean isWithinWindow(K key) {
return System.currentTimeMillis() - key.getLastAccessTime() < window * 1000;
}
}
问题:Caffeine 相比 Guava Cache 有哪些优势?
解析:
Weigher
)和弱引用 / 软引用,减少大对象内存占用getAll
)和流式操作问题:如何处理 Caffeine 与分布式缓存的一致性?
解决方案:
问题:如何优化 Caffeine 在高并发下的锁竞争?
解决方案:
concurrencyLevel
(建议等于 CPU 核心数),减少分段锁竞争ImmutableCache
,避免写入时的锁开销Cache.asMap()
直接访问,写操作通过独立通道处理预热实现方式
// 方式一:手动加载所有预热键
List<String>预热Keys = Arrays.asList("key:1", "key:2", "key:3");
cache.getAll(预热Keys, this::loadBatch); // 批量加载接口
// 方式二:通过 ScheduledExecutor 定时预热
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
cache.invalidate("hotKey:1"); // 主动触发刷新
cache.get("hotKey:2"); // 提前加载热点数据
}, 0, 5, TimeUnit.MINUTES);
集成 Spring Boot Actuator
// 配置类
@Configuration
public class CaffeineConfig {
@Bean
public CacheManagerCustomizer<CaffeineCacheManager> cacheManagerCustomizer() {
return cm -> cm.setStatisticsCollector(CaffeineCacheManager.MetricsStatisticsCollector.INSTANCE);
}
}
// 暴露指标(application.properties)
management.metrics.export.prometheus.enabled=true
management.endpoints.web.exposure.include=prometheus
关键指标说明
指标名称 | 含义 | 采集方式 |
---|---|---|
caffeine_cache_hit_count |
缓存命中次数 | stats().hitCount() |
caffeine_cache_miss_count |
缓存未命中次数 | stats().missCount() |
caffeine_cache_load_duration_seconds |
加载耗时(秒) | stats().loadDuration() |
caffeine_cache_size |
当前缓存条目数 | cache.size() |
本文深入剖析了 Caffeine 的核心架构、淘汰算法与生产实践,其通过 Window TinyLfu 算法与高效并发设计,在本地缓存场景中实现了性能与内存的最佳平衡。在实际应用中,需结合业务读写模式配置过期策略与容量控制,并通过监控体系持续优化缓存命中率。
未来 Caffeine 的发展方向可能包括:
掌握 Caffeine 的原理与优化技巧,不仅能提升单个应用的性能,更为构建分层缓存架构(如本地缓存 + 分布式缓存)提供了坚实的技术基础。