搭建Caffeine+Redis多级缓存机制
作者:moxiaoran5753
本文主要介绍了搭建Caffeine+Redis多级缓存机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
本地缓存的简单实现方案有HashMap,CucurrentHashMap,成熟的本地缓存方案有Guava 与 Caffeine ,企业级应用推荐下面说下两者的区别
0. 核心异同对比
| 特性 | Guava Cache | Caffeine |
|---|---|---|
| 诞生背景 | Google Guava 库的一部分(2011年) | 基于 Guava Cache 重构的现代缓存库(2015+) |
| 性能 | 中等(锁竞争较多) | 极高(优化并发设计,吞吐量提升5~10倍) |
| 内存管理 | 基于 LRU 算法 | 结合 W-TinyLFU 算法(高命中率) |
| 过期策略 | 支持 expireAfterWrite/access | 支持 expireAfterWrite/access + refresh |
| 缓存回收 | 同步阻塞 | 异步非阻塞(后台线程) |
| 监控统计 | 基础统计(命中率等) | 详细统计(命中率、加载时间等) |
| 依赖 | 需引入整个 Guava 库 | 轻量(仅依赖 Caffeine) |
| 社区维护 | 维护模式(新功能少) | 活跃更新(Java 17+ 兼容) |
从上面的比较可知, Caffeine 各方面是优于Guava的,因此在搭建多级缓存机制时,建议使用Caffeine+Redis的组合方案。
业务执行流程:
- 请求优先读取 Caffeine 本地缓存(超快,减少网络IO)。
- 本地缓存未命中 → 读取 Redis 分布式缓存。
- Redis 未命中 → 查询数据库,并回填到两级缓存。
下面介绍下实现方式
注意:下面的实现方式是基于Springboot 2.4+,版本不同,配置上会略有差异
1.maven中引入下面的依赖
<!-- Caffeine 本地缓存 --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <!-- 缓存抽象层 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- redis 缓存操作 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
2.application中进行配置
spring:
cache:
caffeine:
spec: maximumSize=1000,expireAfterWrite=10m # 本地缓存
redis:
time-to-live: 1h # Redis缓存过期时间
# redis 配置
redis:
# 地址
host: 127.0.0.1
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: abc123
# 连接超时时间
timeout: 6000ms # 连接超时时长(毫秒)
jedis:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接3.自定义多级缓存管理器
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration cacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES);
}
@Bean
@Primary // 添加 @Primary 注解指定 CaffeineCacheManager 作为默认的缓存管理器
public CacheManager caffeineCacheManager(Caffeine<Object, Object> caffeine) {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(caffeine);
return manager;
}
@Bean
public RedisCacheManager redisCacheManager(
RedisConnectionFactory redisConnectionFactory,
RedisCacheConfiguration cacheConfiguration) {
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
@Bean
public CacheManager compositeCacheManager(
@Qualifier("caffeineCacheManager") CacheManager caffeineCacheManager,
@Qualifier("redisCacheManager") CacheManager redisCacheManager) {
return new CompositeCacheManager(
caffeineCacheManager,
redisCacheManager
);
}
}
4.业务逻辑层调用
使用示例:
@Service
public class ProductService {
@Autowired
private ProductRepository repository;
// 优先读本地缓存,其次Redis,最后数据库
@Cacheable(cacheNames = "product", key = "#id")
public Product getProductById(Long id) {
return repository.findById(id).orElseThrow();
}
// 更新数据时清除两级缓存
@CacheEvict(cacheNames = "product", key = "#product.id")
public Product updateProduct(Product product) {
return repository.save(product);
}
}手动控制多级缓存
@Service
public class CacheService {
@Autowired
private CacheManager cacheManager;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Product getProductWithManualControl(Long id) {
// 1. 先查本地缓存
Cache caffeineCache = cacheManager.getCache("product");
Product product = caffeineCache.get(id, Product.class);
if (product != null) {
return product;
}
// 2. 查Redis缓存
product = (Product) redisTemplate.opsForValue().get("product:" + id);
if (product != null) {
// 回填本地缓存
caffeineCache.put(id, product);
return product;
}
// 3. 查数据库
product = repository.findById(id).orElseThrow();
// 回填两级缓存
redisTemplate.opsForValue().set("product:" + id, product, Duration.ofHours(1));
caffeineCache.put(id, product);
return product;
}
}缓存一致性
- 使用
@CacheEvict或Redis Pub/Sub同步失效两级缓存。 - 示例:通过 Redis 消息通知其他节点清理本地缓存。
防止缓存击穿
- Caffeine 配置
refreshAfterWrite:
Caffeine.newBuilder()
.refreshAfterWrite(5, TimeUnit.MINUTES)
.build(key -> loadFromRedisOrDb(key));监控统计:
- Caffeine 统计:cache.getNativeCache().stats()
- Redis 统计:INFO commandstats
验证多级缓存
- 本地缓存生效:连续调用同一接口,观察第二次响应时间骤降。
- Redis 缓存生效:重启应用后,首次请求仍快速返回(数据来自Redis)。
到此这篇关于搭建Caffeine+Redis多级缓存机制的文章就介绍到这了,更多相关Caffeine Redis缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
