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对比
特性 | Caffeine | Guava Cache |
---|---|---|
性能 | 更高 | 中等 |
内存效率 | 更优 | 良好 |
淘汰算法 | W-TinyLFU | LRU |
异步刷新 | 支持 | 不支持 |
维护状态 | 活跃 | 维护模式 |
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 最佳实践
- 合理设置缓存大小:根据应用内存情况设置maximumSize
- 选择合适的过期策略:expireAfterWrite适合数据不常变化的场景,expireAfterAccess适合热点数据
- 使用refreshAfterWrite实现自动刷新:对于变化不频繁但需要保持新鲜的数据
- 监控缓存命中率:通过recordStats()开启统计,定期监控缓存效果
- 为不同业务使用不同缓存实例:避免相互影响
- 考虑使用多级缓存:本地缓存+分布式缓存组合使用
五、完整案例:电商商品服务
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本地缓存库,在性能和功能上都表现出色。通过本文的介绍,您应该已经了解了:
- Caffeine的核心特性和优势
- 基本使用方法和配置选项
- 如何与Spring Boot项目集成
- 通过注解和编程式两种方式操作缓存
- 高级特性如异步加载、刷新策略等
- 完整的电商商品服务案例
在实际项目中,合理使用Caffeine可以显著提高系统性能,减少对后端存储的压力。建议根据具体业务场景选择合适的缓存策略,并配合监控统计不断优化缓存配置。