实战指南:如何在电商项目中正确使用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;
}
相关推荐
why1515 小时前
腾讯(QQ浏览器)后端开发
开发语言·后端·golang
浪裡遊5 小时前
跨域问题(Cross-Origin Problem)
linux·前端·vue.js·后端·https·sprint
声声codeGrandMaster5 小时前
django之优化分页功能(利用参数共存及封装来实现)
数据库·后端·python·django
呼Lu噜5 小时前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_1585 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
学c真好玩6 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django
Asthenia04126 小时前
GenericObjectPool——重用你的对象
后端
Piper蛋窝6 小时前
Go 1.18 相比 Go 1.17 有哪些值得注意的改动?
后端
excel6 小时前
招幕技术人员
前端·javascript·后端
盖世英雄酱581366 小时前
什么是MCP
后端·程序员