Skip to content

Caffeine本地缓存组件

约 1980 字大约 7 分钟

2025-05-29

Caffeine是一个高性能的Java本地缓存库。它提供了近乎最优的命中率,并且具有出色的并发性能。

核心特性

  • 高性能 :相比其他Java缓存库,Caffeine在读写性能上表现更优
  • 内存优化 :采用W-TinyLFU淘汰算法,提供近乎最优的命中率
  • 丰富的API :支持多种淘汰策略、刷新机制和统计功能
  • 异步支持 :支持异步加载和异步刷新
  • 与Spring良好集成 :可以轻松集成到Spring、Spring Boot项目中

Caffeine本地缓存组件:介绍、使用步骤与Spring Boot集成案例

一、Caffeine缓存介绍

1.1 什么是Caffeine

Caffeine是一个高性能的Java本地缓存库。它提供了近乎最优的命中率,并且具有出色的并发性能。Caffeine的设计灵感来自Guava Cache和ConcurrentLinkedHashMap,但在性能上做了大量优化。

1.2 Caffeine的核心特性

  • 高性能 :相比其他Java缓存库,Caffeine在读写性能上表现更优
  • 内存优化 :采用W-TinyLFU淘汰算法,提供近乎最优的命中率
  • 丰富的API :支持多种淘汰策略、刷新机制和统计功能
  • 异步支持 :支持异步加载和异步刷新
  • 与Spring良好集成 :可以轻松集成到Spring、Spring Boot项目中

1.3 Caffeine与Guava Cache对比

特性CaffeineGuava Cache
性能更高中等
内存效率更优良好
淘汰算法W-TinyLFULRU
异步刷新支持不支持
维护状态活跃维护模式
Spring集成原生支持需要适配

二、Caffeine基本使用

2.1 添加Maven依赖

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version> <!-- 使用最新版本 -->
</dependency>

2.2 创建缓存实例

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

public class CaffeineDemo {
    public static void main(String[] args) {
        // 创建缓存实例
        Cache<String, Object> cache = Caffeine.newBuilder()
                .initialCapacity(100)          // 初始容量
                .maximumSize(1000)             // 最大容量
                .expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
                .expireAfterAccess(5, TimeUnit.MINUTES) // 访问后5分钟过期
                .build();
        
        // 存入缓存
        cache.put("key1", "value1");
        
        // 获取缓存
        Object value = cache.getIfPresent("key1");
        System.out.println(value);
        
        // 获取或计算(如果不存在)
        Object value2 = cache.get("key2", k -> computeExpensiveValue(k));
        System.out.println(value2);
    }
    
    private static Object computeExpensiveValue(String key) {
        // 模拟耗时计算
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "computed_" + key;
    }
}

2.3 缓存配置选项

Caffeine提供了丰富的配置选项:

Cache<String, Data> cache = Caffeine.newBuilder()
    .initialCapacity(100)                // 初始容量
    .maximumSize(10000)                // 最大条目数
    .maximumWeight(10000)              // 最大权重(需配合weigher使用)
    .weigher((String key, Data data) -> data.getSize()) // 权重计算函数
    .expireAfterWrite(1, TimeUnit.HOURS) // 写入后过期时间
    .expireAfterAccess(30, TimeUnit.MINUTES) // 访问后过期时间
    .refreshAfterWrite(1, TimeUnit.MINUTES) // 写入后刷新时间
    .weakKeys()                         // 弱引用key
    .weakValues()                       // 弱引用value
    .softValues()                       // 软引用value
    .recordStats()                      // 开启统计
    .removalListener((String key, Data data, RemovalCause cause) -> 
        System.out.printf("Key %s was removed (%s)%n", key, cause))
    .build();

三、Spring Boot集成Caffeine

3.1 添加Spring Boot Starter依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>

3.2 配置Caffeine缓存

在application.yml中配置:

spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=500,expireAfterAccess=600s
    cache-names: userCache,productCache,orderCache

或者通过Java配置类:

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CaffeineCacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .recordStats());
        return cacheManager;
    }
    
    // 或者为不同缓存设置不同配置
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        List<CaffeineCache> caches = new ArrayList<>();
        caches.add(new CaffeineCache("userCache", 
            Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build()));
        caches.add(new CaffeineCache("productCache", 
            Caffeine.newBuilder()
                .maximumSize(500)
                .expireAfterWrite(1, TimeUnit.HOURS)
                .build()));
        cacheManager.setCaches(caches);
        return cacheManager;
    }
}

3.3 使用Spring缓存注解

@Service
public class UserService {

    @Cacheable(value = "userCache", key = "#id")
    public User getUserById(Long id) {
        // 模拟数据库查询
        System.out.println("查询数据库获取用户: " + id);
        return new User(id, "用户" + id, "user" + id + "@example.com");
    }

    @CachePut(value = "userCache", key = "#user.id")
    public User updateUser(User user) {
        // 模拟更新数据库
        System.out.println("更新用户到数据库: " + user.getId());
        return user;
    }

    @CacheEvict(value = "userCache", key = "#id")
    public void deleteUser(Long id) {
        // 模拟从数据库删除
        System.out.println("从数据库删除用户: " + id);
    }

    @Caching(evict = {
        @CacheEvict(value = "userCache", allEntries = true)
    })
    public void clearAllCache() {
        System.out.println("清空所有用户缓存");
    }
}

3.4 手动操作缓存

@Service
public class ProductService {
    
    @Autowired
    private CacheManager cacheManager;
    
    public Product getProduct(String productId) {
        // 获取缓存
        Cache cache = cacheManager.getCache("productCache");
        Product product = cache.get(productId, Product.class);
        
        if (product == null) {
            // 缓存不存在,从数据库获取
            product = loadFromDb(productId);
            // 放入缓存
            cache.put(productId, product);
        }
        
        return product;
    }
    
    public void refreshProduct(String productId) {
        Cache cache = cacheManager.getCache("productCache");
        // 使特定key失效
        cache.evict(productId);
        // 或者使用put更新
        // cache.put(productId, loadFromDb(productId));
    }
    
    private Product loadFromDb(String productId) {
        // 模拟数据库查询
        return new Product(productId, "产品" + productId, 100.0);
    }
}

四、高级特性与最佳实践

4.1 异步加载

AsyncLoadingCache<String, Data> asyncCache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .buildAsync(key -> createExpensiveValue(key));

// 获取值(异步)
CompletableFuture<Data> future = asyncCache.get("key1");
Data value = future.get();

4.2 刷新策略

LoadingCache<String, Data> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .refreshAfterWrite(1, TimeUnit.MINUTES)
    .build(key -> createExpensiveValue(key));

4.3 缓存统计

Cache<String, Data> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .recordStats()
    .build();

// 获取统计信息
CacheStats stats = cache.stats();
double hitRate = stats.hitRate();    // 命中率
long evictionCount = stats.evictionCount(); // 淘汰数量

4.4 最佳实践

  1. 合理设置缓存大小:根据应用内存情况设置maximumSize
  2. 选择合适的过期策略:expireAfterWrite适合数据不常变化的场景,expireAfterAccess适合热点数据
  3. 使用refreshAfterWrite实现自动刷新:对于变化不频繁但需要保持新鲜的数据
  4. 监控缓存命中率:通过recordStats()开启统计,定期监控缓存效果
  5. 为不同业务使用不同缓存实例:避免相互影响
  6. 考虑使用多级缓存:本地缓存+分布式缓存组合使用

五、完整案例:电商商品服务

5.1 项目结构

src/main/java
└── com
    └── example
        └── ecommerce
            ├── EcommerceApplication.java
            ├── config
               └── CacheConfig.java
            ├── controller
               └── ProductController.java
            ├── model
               └── Product.java
            └── service
                ├── ProductService.java
                └── impl
                    └── ProductServiceImpl.java

5.2 配置类

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        
        // 商品详情缓存 - 大容量,较长过期时间
        CaffeineCache productCache = new CaffeineCache("productCache",
            Caffeine.newBuilder()
                .maximumSize(10000)
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .recordStats()
                .build());
        
        // 商品库存缓存 - 较小容量,频繁刷新
        CaffeineCache inventoryCache = new CaffeineCache("inventoryCache",
            Caffeine.newBuilder()
                .maximumSize(1000)
                .refreshAfterWrite(1, TimeUnit.MINUTES)
                .recordStats()
                .build());
        
        cacheManager.setCaches(Arrays.asList(productCache, inventoryCache));
        return cacheManager;
    }
}

5.3 服务层实现

@Service
public class ProductServiceImpl implements ProductService {

    // 模拟数据库
    private Map<Long, Product> productDatabase = new ConcurrentHashMap<>();
    private Map<Long, Integer> inventoryDatabase = new ConcurrentHashMap<>();
    
    public ProductServiceImpl() {
        // 初始化测试数据
        for (long i = 1; i <= 100; i++) {
            productDatabase.put(i, new Product(i, "商品" + i, 100.0 + i));
            inventoryDatabase.put(i, 1000);
        }
    }

    @Override
    @Cacheable(value = "productCache", key = "#productId")
    public Product getProductById(Long productId) {
        System.out.println("查询数据库获取商品: " + productId);
        simulateSlowService();
        return productDatabase.get(productId);
    }

    @Override
    @Cacheable(value = "inventoryCache", key = "#productId")
    public Integer getProductInventory(Long productId) {
        System.out.println("查询数据库获取库存: " + productId);
        simulateSlowService();
        return inventoryDatabase.get(productId);
    }

    @Override
    @CachePut(value = "productCache", key = "#product.id")
    public Product updateProduct(Product product) {
        System.out.println("更新商品: " + product.getId());
        productDatabase.put(product.getId(), product);
        return product;
    }

    @Override
    @CacheEvict(value = "productCache", key = "#productId")
    public void removeProduct(Long productId) {
        System.out.println("删除商品: " + productId);
        productDatabase.remove(productId);
    }

    @Override
    @CachePut(value = "inventoryCache", key = "#productId")
    public Integer updateInventory(Long productId, Integer amount) {
        System.out.println("更新库存: " + productId + " -> " + amount);
        inventoryDatabase.put(productId, amount);
        return amount;
    }

    @Override
    @Caching(evict = {
        @CacheEvict(value = "productCache", allEntries = true),
        @CacheEvict(value = "inventoryCache", allEntries = true)
    })
    public void clearAllCaches() {
        System.out.println("清空所有缓存");
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

5.4 控制器

@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.getProductById(id);
    }

    @GetMapping("/{id}/inventory")
    public Integer getInventory(@PathVariable Long id) {
        return productService.getProductInventory(id);
    }

    @PutMapping("/{id}/inventory")
    public Integer updateInventory(@PathVariable Long id, @RequestParam Integer amount) {
        return productService.updateInventory(id, amount);
    }

    @DeleteMapping("/cache")
    public String clearCache() {
        productService.clearAllCaches();
        return "缓存已清空";
    }
}

5.5 监控端点

在application.yml中添加:

management:
  endpoints:
    web:
      exposure:
        include: health,info,caches

然后可以通过/actuator/caches端点查看缓存信息。

六、总结

Caffeine作为新一代的Java本地缓存库,在性能和功能上都表现出色。通过本文的介绍,您应该已经了解了:

  1. Caffeine的核心特性和优势
  2. 基本使用方法和配置选项
  3. 如何与Spring Boot项目集成
  4. 通过注解和编程式两种方式操作缓存
  5. 高级特性如异步加载、刷新策略等
  6. 完整的电商商品服务案例

在实际项目中,合理使用Caffeine可以显著提高系统性能,减少对后端存储的压力。建议根据具体业务场景选择合适的缓存策略,并配合监控统计不断优化缓存配置。