4. 本地缓存方案(Caffeine):Caffeine缓存原理、集成Spring Boot、配置过期策略与最大容量
4.1 Caffeine 缓存核心原理
Caffeine 是当前 Java 生态中性能最优的本地缓存库,其设计基于 W-TinyLFU(Window Tiny Least Frequently Used) 淘汰算法。该算法在传统 LRU(最近最少使用)和 LFU(最不经常使用)之间取得了极佳的平衡,能够有效应对突发流量与热点数据倾斜。
核心设计理念: Caffeine 通过“窗口 + 分段计数”机制,既保留了 LRU 对突发流量的快速响应能力,又利用 LFU 的长期频率统计过滤掉“冷数据”,从而在 Redis 热点削峰场景中作为第一道防线,大幅降低对后端 Redis 的冲击。
4.2 集成 Spring Boot
在 Spring Boot 项目中集成 Caffeine 缓存,推荐使用 Spring Cache 抽象层 + Caffeine 实现的组合方式。这种方式能够保持缓存代码与具体实现解耦,便于后续切换缓存方案。
4.2.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>
<version>3.1.8</version>
</dependency>
4.2.2 启用缓存与配置类
@Configuration
@EnableCaching
public class CaffeineCacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
// 设置默认缓存规格
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(500)
.expireAfterWrite(60, TimeUnit.SECONDS)
.recordStats());
// 支持按名称自定义配置
cacheManager.setCacheNames(Arrays.asList("hotDataCache", "userCache"));
return cacheManager;
}
}
最佳实践: 在 Redis 热点削峰场景中,建议将 Caffeine 作为一级缓存(本地缓存),Redis 作为二级缓存(分布式缓存)。查询时先查 Caffeine,未命中再查 Redis,最后回源数据库。这种“两级缓存”架构能够将热点请求拦截在应用进程内,减少网络 I/O 开销。
4.2.3 使用 @Cacheable 注解
@Service
public class ProductService {
@Cacheable(value = "hotDataCache", key = "#productId",
unless = "#result == null")
public Product getProductById(Long productId) {
// 模拟数据库查询
return productRepository.findById(productId).orElse(null);
}
}
4.3 配置过期策略与最大容量
Caffeine 提供了丰富的过期策略和容量控制参数,合理配置这些参数是保证缓存命中率与内存安全的关键。
| 配置参数 | 方法 | 说明 | 推荐值(热点削峰) |
|---|---|---|---|
| 初始容量 | initialCapacity() |
预分配哈希表大小,减少扩容开销 | 100 ~ 1000 |
| 最大容量 | maximumSize() |
基于条目数量的上限,超出后触发淘汰 | 500 ~ 5000(视 JVM 堆内存而定) |
| 最大权重 | maximumWeight() |
基于自定义权重的上限,适合大小不一的缓存对象 | 按业务对象大小估算 |
| 写入后过期 | expireAfterWrite() |
写入后固定时长过期,适合一致性要求高的场景 | 30s ~ 300s |
| 访问后过期 | expireAfterAccess() |
最后一次访问后固定时长过期,适合热点数据 | 60s ~ 600s |
| 自定义过期 | expireAfter() |
基于 Expiry 接口实现动态过期时间 | 按业务逻辑定制 |
| 弱引用键 | weakKeys() |
使用 WeakReference 存储键,允许 GC 回收 | 谨慎使用,可能影响命中率 |
| 软引用值 | softValues() |
使用 SoftReference 存储值,内存不足时 GC 回收 | 适合大对象缓存 |
4.3.1 典型配置示例
@Bean
public Cache hotDataCache() {
return Caffeine.newBuilder()
.initialCapacity(200)
.maximumSize(1000)
.expireAfterWrite(120, TimeUnit.SECONDS)
.expireAfterAccess(60, TimeUnit.SECONDS)
.recordStats()
.build();
}
容量规划警告: 在 Redis 热点削峰场景中,Caffeine 的最大容量必须根据 JVM 堆内存和业务数据量进行精确估算。建议将 Caffeine 缓存容量控制在堆内存的 5%~10% 以内,避免因本地缓存过大导致 Full GC 频繁或 OOM。生产环境建议通过
-Xmx 参数限制堆内存上限,并配合 recordStats() 监控缓存命中率。
4.3.2 过期策略对比
| 策略 | 淘汰触发条件 | 一致性 | 命中率 | 适用场景 |
|---|---|---|---|---|
| 写入后过期 | 写入时刻 + 固定时长 | 高 | 中 | 数据更新频率固定,如配置信息 |
| 访问后过期 | 最后访问时刻 + 固定时长 | 中 | 高 | 热点数据,如商品详情 |
| 自定义过期 | 根据业务逻辑动态计算 | 灵活 | 最高 | 复杂业务,如不同商品不同过期时间 |
| 最大容量淘汰 | 条目数超过 maximumSize | — | — | 内存保护,必须配置 |
4.4 缓存统计与监控
启用 recordStats() 后,可以通过 CaffeineCache 的 stats() 方法获取缓存运行指标,用于评估缓存效果和调整参数。
// 获取缓存统计信息
CacheStats stats = caffeineCache.stats();
System.out.println("命中率: " + stats.hitRate());
System.out.println("加载次数: " + stats.loadCount());
System.out.println("淘汰总数: " + stats.evictionCount());
System.out.println("平均加载耗时(纳秒): " + stats.averageLoadPenalty());
生产监控建议: 将 Caffeine 的统计指标暴露到 Prometheus 或 Micrometer 中,配置 Grafana 监控面板。重点关注 命中率(hitRate) 和 淘汰数(evictionCount)。当命中率低于 80% 或淘汰数突增时,说明缓存容量或过期策略需要调整。
4.5 两级缓存架构(Caffeine + Redis)
在 Redis 热点削峰项目中,Caffeine 通常与 Redis 配合组成两级缓存。以下是推荐的数据读取流程:
- 查询 Caffeine: 如果命中,直接返回,延迟 < 1ms。
- 查询 Redis: 如果 Caffeine 未命中,查询 Redis,延迟约 1~5ms。
- 回源数据库: 如果 Redis 也未命中,查询数据库,延迟约 10~100ms。
- 回填缓存: 将数据库结果依次写入 Caffeine 和 Redis。
public Product getProductWithMultiLevelCache(Long productId) {
// 1. 查 Caffeine
Product product = caffeineCache.getIfPresent(productId);
if (product != null) {
return product;
}
// 2. 查 Redis
String redisKey = "product:" + productId;
product = redisTemplate.opsForValue().get(redisKey);
if (product != null) {
// 回填 Caffeine
caffeineCache.put(productId, product);
return product;
}
// 3. 查数据库
product = productRepository.findById(productId).orElse(null);
if (product != null) {
// 4. 回填两级缓存
caffeineCache.put(productId, product);
redisTemplate.opsForValue().set(redisKey, product, 300, TimeUnit.SECONDS);
}
return product;
}
削峰效果: 在秒杀或热点活动中,Caffeine 能够承载 80% 以上的重复热点查询,将 Redis 的 QPS 降低 5~10 倍,有效避免 Redis 连接池耗尽和响应超时。