实战指南:如何在电商项目中正确使用Caffeine缓存

实战指南:如何在电商项目中正确使用Caffeine缓存

第一步:引入依赖

在Spring Boot项目中引入Caffeine非常简单,只需要在pom.xml中添加:

xml 复制代码
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>

<!-- 如果要用Spring的缓存抽象 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

第二步:配置缓存实例

我创建了一个CacheConfig类来配置商品缓存:

java 复制代码
@Configuration
@EnableCaching
public class CacheConfig {
    
    // 商品基础信息缓存:最大10000条,写入后30分钟过期
    @Bean
    public Cache<Long, Product> productCache() {
        return Caffeine.newBuilder()
                .maximumSize(10000)
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .recordStats()  // 开启统计功能
                .build();
    }
    
    // 库存缓存:最大50000条,访问后1小时过期
    @Bean 
    public Cache<String, Integer> stockCache() {
        return Caffeine.newBuilder()
                .maximumSize(50000)
                .expireAfterAccess(1, TimeUnit.HOURS)
                .build();
    }
}

第三步:改造商品查询Service

原来的商品服务直接查数据库:

java 复制代码
public Product getProductById(Long id) {
    return productMapper.selectById(id); // 直接访问数据库
}

改造后加入缓存逻辑:

java 复制代码
@Service
@RequiredArgsConstructor
public class ProductService {
    
    private final ProductMapper productMapper;
    private final Cache<Long, Product> productCache;
    
    public Product getProductById(Long id) {
        // 尝试从缓存获取
        Product product = productCache.getIfPresent(id);
        if (product != null) {
            return product;
        }
        
        // 缓存未命中,查询数据库
        product = productMapper.selectById(id);
        if (product != null) {
            // 放入缓存
            productCache.put(id, product);
        }
        
        return product;
    }
    
    // 更优雅的写法:使用LoadingCache
    private final LoadingCache<Long, Product> loadingCache = Caffeine.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .build(this::loadProductFromDB);
            
    private Product loadProductFromDB(Long id) {
        return productMapper.selectById(id);
    }
    
    public Product getProductByIdPro(Long id) {
        return loadingCache.get(id);
    }
}

第四步:处理缓存一致性

当商品信息更新时,需要同步更新缓存:

java 复制代码
@Transactional
public void updateProduct(Product product) {
    // 更新数据库
    productMapper.updateById(product);
    
    // 更新缓存
    productCache.put(product.getId(), product);
    
    // 更严谨的做法:先失效再更新
    // productCache.invalidate(product.getId());
}

第五步:添加缓存统计

通过recordStats()开启的统计功能,我们可以监控缓存效果:

java 复制代码
@Scheduled(fixedRate = 60000) // 每分钟打印一次统计
public void printCacheStats() {
    CacheStats stats = productCache.stats();
    log.info("缓存命中率: {}. 加载次数: {}, 平均加载时间: {}ms",
            stats.hitRate(),
            stats.loadCount(),
            stats.averageLoadPenalty() / 1000000);
}

第六步:高级功能 - 异步刷新

对于重要的基础数据,我们可以设置自动刷新:

java 复制代码
// 在CacheConfig中添加
@Bean
public LoadingCache<Long, Product> productRefreshCache(ProductMapper productMapper) {
    return Caffeine.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .refreshAfterWrite(10, TimeUnit.MINUTES) // 写入10分钟后可刷新
            .build(productMapper::selectById);
}

实际效果对比

优化前: • 平均响应时间:78ms • 数据库QPS:3000

优化后: • 平均响应时间:12ms(缓存命中时) • 数据库QPS:降至约500(缓存命中率83%) • 高峰期CPU使用率下降40%

踩坑经验

  1. 缓存穿透:有恶意请求随机ID,解决方案:
java 复制代码
// 在build方法中添加空值处理
.build(id -> {
    Product p = productMapper.selectById(id);
    return p == null ? Product.EMPTY : p; // 使用特殊对象标记空值
});
  1. 批量查询优化
java 复制代码
public Map<Long, Product> batchGetProducts(Set<Long> ids) {
    return productCache.getAll(ids, 
            missingIds -> productMapper.selectBatchIds(missingIds));
}
  1. 内存监控:通过JMX暴露缓存指标:
java 复制代码
@Bean
public CaffeineCacheManager cacheManager() {
    CaffeineCacheManager manager = new CaffeineCacheManager();
    manager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(10000)
            .recordStats()
            .jmxEnabled(true)); // 开启JMX监控
    return manager;
}
相关推荐
立心者02 小时前
SpringBoot中使用TOTP实现MFA(多因素认证)
java·spring boot·后端
AskHarries2 小时前
SEO 页面怎么生成
后端
爱吃牛肉的大老虎3 小时前
Rust对象之结构体,枚举,特性
开发语言·后端·rust
石榴3 小时前
NestJS 的请求到底经过了什么:装饰器、守卫、拦截器、管道与中间件如何配合
后端
Gopher_HBo3 小时前
moby-client客户端
后端
杨运交3 小时前
[055][调度模块]Spring动态任务调度框架的设计与实现
java·后端·spring
卷福同学4 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
Csvn5 小时前
📊 SQL 入门 Day 11:CASE 表达式:SQL 里的 if-else 魔法
后端·sql
QQ_21696290965 小时前
Spring Boot 养老院管理系统:从入住、护理到费用结算的全流程实现(源码可领)
java·spring boot·后端
万少8 小时前
DeepSeek-V4-Flash 正式版上线了,但这 3 个坑我帮你提前踩了
前端·javascript·后端