在Java商城项目中,结合Caffeine和Redis实现热点数据缓存,可以通过多级缓存架构和访问频率统计机制来实现。以下是具体实现方案:
核心思路
- 两级缓存结构:Caffeine(本地缓存) + Redis(分布式缓存)
- 热点识别:利用Caffeine的访问统计功能追踪高频访问数据
- 数据同步:将识别出的热点数据主动推送到Redis
实现步骤
1. 依赖配置
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
2. 配置带统计的Caffeine缓存
@Configuration
public class CaffeineConfig {
@Bean
public Cache<String, Product> productCache() {
return Caffeine.newBuilder()
.maximumSize(1000) // 本地缓存最大容量
.recordStats() // 启用统计功能
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后过期时间
.build();
}
}
3. 热点数据识别与缓存服务
@Service
public class ProductCacheService {
private final Cache<String, Product> caffeineCache;
private final RedisTemplate<String, Product> redisTemplate;
private final ProductMapper productMapper; // 数据库访问
// 热点阈值配置(如:5分钟内访问50次)
private static final int HOT_THRESHOLD = 50;
private static final Duration HOT_PERIOD = Duration.ofMinutes(5);
@Scheduled(fixedRate = 5 * 60 * 1000) // 每5分钟检测一次
public void detectAndCacheHotProducts() {
Map<String, Product> cachedProducts = caffeineCache.asMap();
long currentTime = System.currentTimeMillis();
cachedProducts.forEach((productId, product) -> {
// 获取该商品在统计窗口内的访问次数
long hitCount = caffeineCache.stats().hitCount();
if (hitCount >= HOT_THRESHOLD) {
// 标记为热点并推送到Redis
product.setHot(true);
redisTemplate.opsForValue().set(
"hot:product:" + productId,
product,
30, TimeUnit.MINUTES // 设置较长TTL
);
log.info("检测到热点商品 {},访问次数: {}", productId, hitCount);
}
});
}
public Product getProduct(String productId) {
// 1. 先查本地缓存
Product product = caffeineCache.getIfPresent(productId);
if (product != null) return product;
// 2. 查询Redis热点缓存
product = redisTemplate.opsForValue().get("hot:product:" + productId);
if (product != null) {
caffeineCache.put(productId, product); // 回填本地缓存
return product;
}
// 3. 查询数据库
product = productMapper.selectById(productId);
if (product != null) {
// 存入本地缓存(所有商品)
caffeineCache.put(productId, product);
// 异步检查是否热点
if (isPotentialHot(productId)) {
redisTemplate.opsForValue().set(
"hot:product:" + productId,
product,
10, TimeUnit.MINUTES // 初始设置较短TTL
);
}
}
return product;
}
private boolean isPotentialHot(String productId) {
// 实现简单预测逻辑(例如:新品/促销商品)
return productId.startsWith("NEW_") || productId.endsWith("_SALE");
}
}
4. 优化点
- 分级阈值策略:
// 多级热点判定
if (hitCount > 100) { // 超级热点
redisTemplate.expire("hot:product:"+productId, 2, TimeUnit.HOURS);
} else if (hitCount > 50) { // 普通热点
redisTemplate.expire("hot:product:"+productId, 30, TimeUnit.MINUTES);
}
- 分布式协调(多实例场景):
// 使用Redis发布订阅同步热点信息
redisTemplate.convertAndSend("hot-product-channel", productId);
// 监听器
@RedisListener(channel = "hot-product-channel")
public void onHotProduct(String productId) {
if (!caffeineCache.asMap().containsKey(productId)) {
Product p = productMapper.selectById(productId);
caffeineCache.put(productId, p);
}
}
- 淘汰机制:
// 当Caffeine缓存淘汰时回调
caffeineCache.policy().eviction().ifPresent(eviction -> {
eviction.addListener((key, value, cause) -> {
if (cause == RemovalCause.SIZE) { // 因空间不足被淘汰
redisTemplate.delete("hot:product:" + key);
}
});
});
架构优势
- 性能优先:99%的请求由本地缓存响应(Caffeine读写性能在微秒级)
- 动态感知:通过Caffeine的
recordStats
实现实时访问追踪 - 资源优化:
- 本地缓存:高频访问数据
- Redis缓存:真正热点数据
- 数据库:长尾数据
- 平滑降级:Redis故障时仍可依靠本地缓存提供服务
监控指标
// 暴露缓存统计信息
public CacheStats getCacheStats() {
return caffeineCache.stats();
}
// 典型监控指标:
// - hitRate(): 缓存命中率
// - evictionCount(): 淘汰数量
// - averageLoadPenalty(): 平均加载时间
注意事项
- 缓存一致性:
- 商品更新时需同步清理两级缓存
- 使用
@CacheEvict
或消息队列保证一致性
- 内存控制:
- 本地缓存使用
maximumWeight
防止OOM - Redis设置合理的TTL避免数据膨胀
- 本地缓存使用
- 冷启动问题:
- 预加载潜在热点商品(如促销商品)
- 采用布隆过滤器减少缓存穿透
通过这种设计,系统能够自动识别热点商品(如秒杀商品、爆款商品),并在多级缓存体系中进行高效管理,显著提升高并发场景下的性能表现。