目录
给大家介绍一下本地缓存的使用,因为二级缓存是开发中必须要掌握的,可以很大程度上提供数据获取的性能,本篇主要给大家介绍的是caffeine。
什么样的数据适合存到缓存里?最简单的就一句话:读多写少的数据。数据不需要频繁更改的数据就很适合存到缓存中,因为它在缓存里读取得更快,不需要在想数据库发送网络请求。当然还有一个重要的因素就是数据的一致性问题,需强一致性的数据,如果非必要,就不用存到缓存当中。
废话不多说,直接先教大家怎么使用,再细讲里面的配置。
直接实例化的方式:快速使用
1、引入依赖:
注意如果是jdk8的必须要引入2.x.x版本的caffeine,否则跑不了。
<!-- 本地缓存 Caffeine jdk8用2.x版本-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
2、在业务层(service层)全局变量里定义设置Caffeine,变量要用final修饰。这样每个service里的方法都能使用。
//本地缓存配置
private final Cache<String, String> LOCAL_CACHE =
Caffeine.newBuilder().initialCapacity(1024) //初始容量
.maximumSize(10000L) //最大容量
// 缓存 5 分钟移除
.expireAfterWrite(5L, TimeUnit.MINUTES)
.build();
3、业务中如何使用?
@Override
public Page<resultVO> getVOPage(queryRequest queryRequest) {
//...业务逻辑
//一、构建缓存key,将请求实体转成json格式,然后经过MD5压缩作为key
String condition = JSONUtil.toJsonStr(queryRequest);
String hashKey = DigestUtils.md5DigestAsHex(condition.getBytes());
String cacheKey = String.format("picture:getPictureListVO:%s", hashKey);
//二、查本地缓存:
Page<resultVO> cachePage;
String cacheValue = LOCAL_CACHE.getIfPresent(cacheKey);
if (cacheValue != null) {
cachePage = JSONUtil.toBean(cacheValue, Page.class);
return cachePage;
}
//三、本地缓存没有,去查询下一级缓存或者数据库查到数据后,将数据放到本地缓存
String jsonStr = userDao.select() //模拟去别的地方查到了数据
LOCAL_CACHE.put(cacheKey, jsonStr);
//......其他业务逻辑
}
spring集成的方式:
先引入依赖:
<!-- Spring Cache支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Caffeine核心依赖 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
1、配置文件中配置:
在配置文件中直接定义Caffeine的参数,适合简单场景。
application.properties示例:
# 指定缓存类型为Caffeine
spring.cache.type=caffeine
# 配置缓存名称和参数(多个缓存用逗号分隔)
spring.cache.cache-names=users,dynamics
# 配置Caffeine的参数(以逗号分隔)
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s,softValues=true
application.yml示例:
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=500,expireAfterAccess=600s,softValues=true
cache-names:
- users
- dynamics
2、通过配置类配置
通过@Configuration类灵活配置多个缓存实例,适合复杂场景。
@Configuration
@EnableCaching
public class CaffeineConfig {
// 配置默认的缓存管理器
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager("users", "dynamics");
manager.setCaffeine(caffeineConfig());
return manager;
}
// 定义Caffeine的通用配置
private Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
// 最大缓存条目数
.maximumSize(500)
// 写入后过期时间(10分钟)
.expireAfterWrite(10, TimeUnit.MINUTES)
// 访问后刷新过期时间(需配合refreshAfterWrite)
//.expireAfterAccess(5, TimeUnit.MINUTES)
//.refreshAfterWrite(2, TimeUnit.MINUTES)
// 使用软引用(内存不足时回收)
.softValues(true)
// 初始容量(根据预估调整)
.initialCapacity(100);
}
}
3、核心参数详解
1.容量控制:
参数 | 说明 | 示例 |
---|---|---|
maximumSize | 最大缓存条目数,超过后触发淘汰策略。 | .maximumSize(1000) |
maximumWeight | 按权重总和控制容量,需结合weigher定义权重计算逻辑。 | .maximumWeight(1000) |
weigher | 定义每个条目的权重,例如:Weigher<String, Object> (k, v) -> v.length() | .weigher((k, v) -> v.length()) |
initialCapacity | 初始容量,避免频繁扩容。 | .initialCapacity(100) |
2. 过期策略
参数 | 说明 | 示例 |
---|---|---|
expireAfterWrite | 写入后过期时间(推荐)。 | .expireAfterWrite(10, MINUTES) |
expireAfterAccess | 访问后过期时间(需配合refreshAfterWrite防止频繁访问不被淘汰)。 | .expireAfterAccess(5, MINUTES) |
refreshAfterWrite | 定期刷新过期时间(需与expireAfterAccess配合使用)。 | .refreshAfterWrite(2, MINUTES) |
3. 内存管理
参数 | 说明 | 示例 |
---|---|---|
softValues | 使用软引用,内存不足时回收。 | .softValues(true) |
weakKeys/weakValues | 使用弱引用,对象可能随时被GC回收(慎用)。 | .weakKeys(true) |
recordStats | 开启统计功能,获取命中率、加载时间等指标。 | .recordStats() |
4. 完整使用示例
@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager("userCache");
manager.setCaffeine(Caffeine.newBuilder()
.maximumWeight(1000) // 总权重不超过1000
.weigher((k, v) -> ((User)v).getSize()) // 用户对象的size属性为权重
.expireAfterWrite(15, TimeUnit.MINUTES)
.recordStats()
);
return manager;
}
@Service
public class UserService {
@Cacheable(value = "userCache", key = "#userId")
public User getUserById(String userId) {
// 数据库查询逻辑
return userRepository.findById(userId);
}
}
二者区别:
生命周期
(1)实例化方式:
- 作用域:缓存实例是业务类的成员变量,生命周期与业务类绑定。
- 线程安全:Caffeine本身是线程安全的,但需注意业务逻辑中对缓存的并发访问。
(2)Spring集成方式:
- 作用域:通过
CacheManager
管理的缓存实例是Spring的Bean,生命周期由Spring容器控制。 - 线程安全:同样线程安全,但Spring会自动处理缓存的初始化和销毁。
参数配置灵活性
(1)实例化方式
- 硬编码参数:修改配置需重新编译代码,不适合生产环境动态调整。
- 无法利用Spring的占位符:如@Value("${cache.max-size}")无法直接使用。
(2)Spring集成方式
- 支持外部化配置:可通过application.properties或@Value动态注入参数:
spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=5m
使用场景
场景 | 直接实例化方式 | Spring集成方式 |
---|---|---|
简单缓存需求 | 适合(如单个缓存实例,无需注解) | 适用,但可能“过度设计” |
复杂缓存管理 | 需重复代码,难以维护 | 推荐,集中配置、多缓存实例支持 |
需要监控与统计 | 需手动实现 | 自动集成,便于监控 |
需要动态配置 | 不支持 | 支持通过application.properties配置 |
与Spring生态集成 | 无法利用Spring注解 | 完全兼容Spring的缓存抽象层 |
缓存key的设计
1、唯一性
- 每个key必须唯一,避免不同业务场景或数据的key冲突。
- 示例:user:1001:profile(用户ID+业务类型)。
2、可读性
- key的命名需清晰表达其含义,方便维护和排查问题。
- 示例:product:category:electronics(业务模块+数据类型+具体分类)。
3、简洁性
- key不宜过长,减少内存占用和网络传输开销。
- 示例:用缩写(如u:1001代替user:1001)或哈希值(如md5(业务参数))缩短长度。
4、结构化
- 采用分层结构,通过分隔符(如:)将不同层级的信息组合起来。
- 示例:module:entity:type:id:version。