实战指南:如何在电商项目中正确使用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;
}
相关推荐
ONE_PUNCH_Ge12 分钟前
Go 语言泛型
开发语言·后端·golang
良许Linux25 分钟前
DSP的选型和应用
后端·stm32·单片机·程序员·嵌入式
不光头强32 分钟前
spring boot项目欢迎页设置方式
java·spring boot·后端
怪兽毕设1 小时前
基于SpringBoot的选课调查系统
java·vue.js·spring boot·后端·node.js·选课调查系统
学IT的周星星1 小时前
Spring Boot Web 开发实战:第二天,从零搭个“会卖萌”的小项目
spring boot·后端·tomcat
郑州光合科技余经理1 小时前
可独立部署的Java同城O2O系统架构:技术落地
java·开发语言·前端·后端·小程序·系统架构·uni-app
Remember_9931 小时前
Spring 事务深度解析:实现方式、隔离级别与传播机制全攻略
java·开发语言·数据库·后端·spring·leetcode·oracle
好好研究2 小时前
SpringBoot整合SpringMVC
xml·java·spring boot·后端·mvc
曹轲恒2 小时前
SpringBoot整合SpringMVC(末)
java·spring boot·后端
小马爱打代码2 小时前
Spring Boot:邮件发送生产可落地方案
java·spring boot·后端