实战指南:如何在电商项目中正确使用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;
}
相关推荐
Nejosi_念旧2 小时前
解读 Go 中的 constraints包
后端·golang·go
风无雨2 小时前
GO 启动 简单服务
开发语言·后端·golang
小明的小名叫小明2 小时前
Go从入门到精通(19)-协程(goroutine)与通道(channel)
后端·golang
斯普信专业组2 小时前
Go语言包管理完全指南:从基础到最佳实践
开发语言·后端·golang
一只叫煤球的猫4 小时前
【🤣离谱整活】我写了一篇程序员掉进 Java 异世界的短篇小说
java·后端·程序员
你的人类朋友5 小时前
🫏光速入门cURL
前端·后端·程序员
aramae7 小时前
C++ -- STL -- vector
开发语言·c++·笔记·后端·visual studio
lifallen7 小时前
Paimon 原子提交实现
java·大数据·数据结构·数据库·后端·算法
舒一笑8 小时前
PandaCoder重大产品更新-引入Jenkinsfile文件支持
后端·程序员·intellij idea
PetterHillWater8 小时前
AI编程之CodeBuddy的小试
后端·aigc