当前位置: 首页 > backend >正文

搭建Caffeine+Redis多级缓存机制

本地缓存的简单实现方案有HashMap,CucurrentHashMap,成熟的本地缓存方案有Guava 与 Caffeine ,企业级应用推荐下面说下两者的区别

1. 核心异同对比

特性Guava CacheCaffeine
诞生背景Google Guava 库的一部分(2011年)基于 Guava Cache 重构的现代缓存库(2015+)
性能中等(锁竞争较多)极高(优化并发设计,吞吐量提升5~10倍)
内存管理基于 LRU 算法结合 W-TinyLFU 算法(高命中率)
过期策略支持 expireAfterWrite/access支持 expireAfterWrite/access + refresh
缓存回收同步阻塞异步非阻塞(后台线程)
监控统计基础统计(命中率等)详细统计(命中率、加载时间等)
依赖需引入整个 Guava 库轻量(仅依赖 Caffeine)
社区维护维护模式(新功能少)活跃更新(Java 17+ 兼容)

从上面的比较可知, Caffeine 各方面是优于Guava的,因此在搭建多级缓存机制时,建议使用Caffeine+Redis的组合方案。

业务执行流程

  1. 请求优先读取 Caffeine 本地缓存(超快,减少网络IO)。

  2. 本地缓存未命中 → 读取 Redis 分布式缓存

  3. 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# 端口,默认为6379port: 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 {@Beanpublic 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;}@Beanpublic 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;}@Beanpublic RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory,RedisCacheConfiguration cacheConfiguration) {return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfiguration).build();}@Beanpublic CacheManager compositeCacheManager(@Qualifier("caffeineCacheManager") CacheManager caffeineCacheManager,@Qualifier("redisCacheManager") CacheManager redisCacheManager) {return new CompositeCacheManager(caffeineCacheManager,redisCacheManager);}
}

4.业务逻辑层调用

使用示例:

@Service
public class ProductService {@Autowiredprivate 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 {@Autowiredprivate CacheManager cacheManager;@Autowiredprivate 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;}
}
  1. 缓存一致性

    • 使用 @CacheEvict 或 Redis Pub/Sub 同步失效两级缓存。

    • 示例:通过 Redis 消息通知其他节点清理本地缓存。

  2. 防止缓存击穿

    • Caffeine 配置 refreshAfterWrite

Caffeine.newBuilder().refreshAfterWrite(5, TimeUnit.MINUTES).build(key -> loadFromRedisOrDb(key));

3.监控统计:

Caffeine 统计:cache.getNativeCache().stats()

Redis 统计:INFO commandstats

4. 验证多级缓存
本地缓存生效:连续调用同一接口,观察第二次响应时间骤降。
Redis 缓存生效:重启应用后,首次请求仍快速返回(数据来自Redis)。

 

http://www.xdnf.cn/news/6189.html

相关文章:

  • ChatGPT 能“记住上文”的原因
  • nputop:昇腾 NPU 交互式监控工具
  • 基于 NanoDet 的工厂巡检机器人目标识别系统研究与实现​
  • Fluent Bit持久化配置:保障数据可靠传输的关键
  • MVCC:数据库并发控制的利器
  • 【计算机哲学故事1-5】版本更新:拒绝停滞,成长是最好的修复
  • 部署GraphRAG配置Neo4j实现知识图谱可视化【踩坑经历】
  • 【SSL证书系列】https双向认证中客户端认证的原理
  • 边缘计算网关工业物联网应用:空压机远程运维监控管理
  • 自动化 NuGet 包打包与上传:完整批处理脚本详解(含 SVN 支持)
  • MySQL基础原理
  • python之Pandas合并数据终极指南:pd.concat参数详解
  • IDEA 新建 SpringBoot 项目时,没有高版本 SpringBoot 可选
  • Android逆向学习(十) IDA逆向编辑Android so文件
  • 为什么要选择七彩喜数字康养平台?加盟后有何优势?
  • 计算机网络:手机和基站之间的通信原理是什么?
  • 高德地图在Vue3中的使用方法
  • ACM算法
  • ubuntu清除缓存
  • rhel8.1 无法安装应用(提示需要注册系统)
  • 【Ansys 2023 R2 Icepak】 风扇位置优化
  • 作业帮Android面试题及参考答案
  • 第5章 运算符、表达式和语句
  • 下载的旧版的jenkins,为什么没有旧版的插件
  • 【面试 · 四】vue2、vue3、react区别
  • 高防ip支持哪些网络协议
  • 【 Java开发枚举值规范】
  • 打卡DAY25
  • whonix系统详解
  • select、poll、epoll