使用Caffeine实现高效本地缓存
1. Caffeine简介
Caffeine是一个高性能的Java缓存库,由Guava缓存的原作者Ben Manes开发。它提供了近乎最优的命中率,并且具有出色的并发性能。Caffeine的设计借鉴了Guava Cache和ConcurrentLinkedHashMap,但在性能和功能上都有显著提升。
主要特性:
- 自动加载缓存项
- 基于大小的淘汰策略
- 基于时间的淘汰策略
- 异步刷新
- 统计信息收集
- 事件监听
2. 为什么要使用本地缓存?
在分布式系统中,使用本地缓存可以带来以下好处:
- 减少远程调用:将频繁访问的数据缓存在本地,减少对远程服务或数据库的调用
- 提升性能:本地内存访问速度远快于网络请求
- 降低系统负载:减少对后端服务的压力
- 提高系统可用性:即使远程服务不可用,本地缓存仍可提供服务
- 降低成本:减少网络带宽和数据库资源的消耗
3. 本地缓存的优缺点
优点:
- 访问速度快
- 实现简单
- 不依赖外部服务
- 适合缓存热点数据
缺点:
- 内存占用较大
- 数据一致性难以保证
- 不适合缓存大量数据
- 集群环境下缓存同步困难
4. 常用缓存淘汰策略
- FIFO(First In First Out):先进先出,淘汰最早进入缓存的数据
- LRU(Least Recently Used):最近最少使用,淘汰最久未使用的数据
- LFU(Least Frequently Used):最少使用,淘汰使用频率最低的数据
- TTL(Time To Live):设置缓存项的生存时间,到期自动淘汰
- 基于大小的淘汰:当缓存达到最大容量时,淘汰部分数据
5. Caffeine在Spring Boot中的使用
5.1 添加依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
5.2 配置缓存
@Configuration
public class CacheConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
.maximumSize(1000) // 最大缓存1000个对象
.recordStats() // 开启统计功能
.build();
}
}
5.3 使用缓存
@Service
public class UserService {
@Autowired
private Cache<String, User> caffeineCache;
public User getUser(String userId) {
return caffeineCache.get(userId, key -> {
// 缓存未命中时,从数据库加载
return userRepository.findById(userId).orElse(null);
});
}
}
6. Caffeine API列表
API | 功能 |
---|---|
Caffeine.newBuilder() | 创建缓存构建器 |
maximumSize(long) | 设置最大缓存大小 |
expireAfterWrite(long, TimeUnit) | 设置写入后过期时间 |
expireAfterAccess(long, TimeUnit) | 设置访问后过期时间 |
refreshAfterWrite(long, TimeUnit) | 设置写入后刷新时间 |
recordStats() | 开启统计功能 |
build() | 构建缓存实例 |
get(K, Function<? super K, ? extends V>) | 获取缓存值,未命中时执行加载函数 |
put(K, V) | 手动添加缓存项 |
invalidate(K) | 移除指定缓存项 |
invalidateAll() | 移除所有缓存项 |
asMap() | 将缓存转换为ConcurrentMap |
stats() | 获取缓存统计信息 |
7. SpringBoot集成Caffeine+Redis实现一二级缓存
7.1 配置类
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000));
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)))
.build();
return new TwoLevelCacheManager(caffeineCacheManager, redisCacheManager);
}
}
7.2 二级缓存管理器
public class TwoLevelCacheManager implements CacheManager {
private final CacheManager level1CacheManager;
private final CacheManager level2CacheManager;
public TwoLevelCacheManager(CacheManager level1CacheManager, CacheManager level2CacheManager) {
this.level1CacheManager = level1CacheManager;
this.level2CacheManager = level2CacheManager;
}
@Override
public Cache getCache(String name) {
return new TwoLevelCache(level1CacheManager.getCache(name),
level2CacheManager.getCache(name));
}
@Override
public Collection<String> getCacheNames() {
return level1CacheManager.getCacheNames();
}
}
7.3 二级缓存实现
public class TwoLevelCache implements Cache {
private final Cache level1Cache;
private final Cache level2Cache;
public TwoLevelCache(Cache level1Cache, Cache level2Cache) {
this.level1Cache = level1Cache;
this.level2Cache = level2Cache;
}
@Override
public String getName() {
return level1Cache.getName();
}
@Override
public Object getNativeCache() {
return level1Cache.getNativeCache();
}
@Override
public ValueWrapper get(Object key) {
ValueWrapper value = level1Cache.get(key);
if (value == null) {
value = level2Cache.get(key);
if (value != null) {
level1Cache.put(key, value.get());
}
}
return value;
}
@Override
public void put(Object key, Object value) {
level1Cache.put(key, value);
level2Cache.put(key, value);
}
@Override
public void evict(Object key) {
level1Cache.evict(key);
level2Cache.evict(key);
}
@Override
public void clear() {
level1Cache.clear();
level2Cache.clear();
}
}
7.4 使用示例
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getProduct(String id) {
// 从数据库获取产品信息
return productRepository.findById(id).orElse(null);
}
}
总结
本文详细介绍了如何使用Caffeine实现高效的本地缓存,包括其核心特性、使用场景、在Spring Boot中的集成方法,以及如何与Redis结合实现一二级缓存。通过合理使用本地缓存,可以显著提升系统性能,降低后端服务压力。在实际项目中,建议根据具体业务场景选择合适的缓存策略,并注意缓存一致性问题。