文章目录
前言
我不生产知识,我只是知识的搬运工,以下是我使用 spring-boot 整合 jetCache 的一些经验分享。
一、概述
github:https://github.com/alibaba/jetcache
官方文档:https://github.com/alibaba/jetcache/wiki/Home_CN
官网上是这么描述 JetCache的:
JetCache是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。 JetCache提供了比SpringCache更加强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,还提供了 Cache
接口用于手工缓存操作。 当前有四个实现,RedisCache
、TairCache
(此部分未在github开源)、CaffeineCache
(in memory)和一个简易的 LinkedHashMapCache
(in memory),要添加新的实现也是非常简单的。
全部特性:
- 通过统一的API访问Cache系统
- 通过注解实现声明式的方法缓存,支持TTL和两级缓存
- 通过注解创建并配置Cache实例
- 针对所有Cache实例和方法缓存的自动统计
- Key的生成策略和Value的序列化策略是可以配置的
- 分布式缓存自动刷新,分布式锁 (2.2+)
- 异步Cache API (2.2+,使用Redis的lettuce客户端时)
- Spring Boot支持
jetCache
对 SpringCache 进行了封装,所以 JetCache 的使用和 SpringCache 的使用也是非常的相似。并且 jetCache 在原有功能基础上实现了多级缓存、缓存统计、自动刷星、异步调用、数据报表等功能。
jetCache 设定了本地缓存与远程缓存的多级缓存解决方案
- 本地缓存(local)
- linkedHashMap
- Caffeine
- 远程缓存(remote)
- Redis
- Tair
本地缓存一般使用的是 linkedHashMap
,远程缓存一般使用的是 Redis
。
功能简述:
- 有接口与 api 两种缓存实现,接口使用方便,api 使用类似于 map,可以更细粒度的控制缓存
- @CreateCache
- @Cached - 可选择内存缓存、分布式缓存,或者同时存在,
同时存在时优先访问内存
- 自动刷新策略,防止某个缓存失效,突然访问量增大,导致数据库挂掉(缓存雪崩)
- 有不严格的分布式锁,对同一 key,全局只有一台机器自动刷新
二、使用步骤
1. 引入依赖坐标
可以去 maven 仓库 去寻找 jetCache 所对应的坐标,直接搜索 jetcache
,就能找到
一般我们选择 JetCache Starter Redis
这个坐标。
点击进入,选择 和自己项目不冲突
的版本。(PS:这个我也不好说,我是一个个试出来的)
复制坐标,粘到项目之中即可
<!-- https://mvnrepository.com/artifact/com.alicp.jetcache/jetcache-starter-redis -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.6.0</version>
</dependency>
2. 编写配置文件
上面说过 JetCache 不仅支持本地本地缓存,也支持远程缓存。
yml 配置如下:(可参考官方配置说明)
# jetCache 配置
jetcache:
# 指定统计间隔,以分钟为单位,默认0:表示不统计
statIntervalMinutes: 15
# areaName 是否作为缓存 key 前缀,默认 True
areaInCacheName: false
# 本地缓存方案
local:
# 区域 area 名
default:
# 缓存类型,已支持可选:linkedhashmap、caffeine
type: linkedhashmap
# key 转换器的全局配置,当前只有:fastjson
keyConvertor: fastjson
# 每个缓存实例的最大元素的全局配置,仅 local 类型的缓存需要指定
limit: 100
# jetCache2.2 以上,以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。0表示不使用这个功能
expireAfterAccessInMillis: 30000
# 远程缓存方案
remote:
# 区域 area 名
default:
# 缓存类型,已支持可选:redis、tair
type: redis
# key 转换器的全局配置,当前只有:fastjson
keyConvertor: fastjson
# 序列化器的全局配置。仅remote类型的缓存需要指定,可选java和kryo
valueEncoder: java
valueDecoder: java
# redis ip 地址
host: 127.0.0.1
# redis 端口号
port: 6379
# host 和 port 也可以用 url 配置:如下
# uri: redis://localhost:6379/1?timeout=5s
# 如果 redis 有设置密码需要加上 password
# password:
# 以毫秒为单位指定超时时间的全局配置
expireAfterWriteInMillis: 5000
# 集群模式
# mode: MasterSlave # (主从模式)
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
3. 注解开启缓存
在启动类上使用 @EnableCreateCacheAnnotation
激活 @CreateCache
,使用 @EnableMethodCache
激活 @Cached
,同时 @EnableMethodCache
还需要指定扫描包的路径。
4. @CreateCach 的使用
通过 @CreateCache
注解创建 Cache 实例,这种灵活性比较高。
在Spring bean中使用@CreateCache注解创建一个Cache实例。例如:
@CreateCache(expire = 100)
private Cache<Long, UserDO> userCache;
属性 | 默认值 | 说明 |
---|---|---|
area | “default” | 如果需要连接多个缓存系统,可在配置多个cache area,这个属性指定要使用的那个area的name |
name | 未定义 | 指定缓存的名称,不是必须的,如果没有指定,会使用类名+方法名。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。如果两个@CreateCache 的name 和area 相同,它们会指向同一个Cache 实例 |
expire | 未定义 | 该Cache实例的默认超时时间定义,注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取无穷大 |
timeUnit | TimeUnit.SECONDS | 指定expire的单位 |
cacheType | CacheType.REMOTE | 缓存的类型,包括CacheType.REMOTE 、CacheType.LOCAL 、CacheType.BOTH 。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存 |
localLimit | 未定义 | 如果cacheType为CacheType.LOCAL或CacheType.BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取100 |
serialPolicy | 未定义 | 如果cacheType为CacheType.REMOTE或CacheType.BOTH,指定远程缓存的序列化方式。JetCache内置的可选值为SerialPolicy.JAVA和SerialPolicy.KRYO。注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取SerialPolicy.JAVA |
keyConvertor | 未定义 | 指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,JetCache内置的可选值为KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不转换,FASTJSON通过fastjson将复杂对象KEY转换成String。如果注解上没有定义,则使用全局配置。 |
列举:我在我的 TestServiceImpl
类中定义了一个 jetCache 的缓存,并且用 @CreateCache
注解表示该对象是用于缓存的对象,代码如下:
// 定义一个 jetCache 缓存,名称为 jetCache_,过期时间 3600 秒
// timeUnit 默认为 TimeUnit.SECONDS,可换成其它时间单位
// area 使用的是默认配置(可不写),如果想要使用其它缓存系统,必须指定名称
@CreateCache(area = "default",name = "jetCache_",expire = 3600,timeUnit = TimeUnit.SECONDS)
private Cache<String,String> jetCache;
这里的缓存的 key 和 value 的类型可根据需要自定义
,Cache 的导包使用 com.alicp.jetcahe
然后我在这个实现类中写了两个方法,一个往缓存中存值,一个往缓存中取值,分别对应 /put-cache
、 /get-cache
两个接口,代码如下
@CreateCache(area = "default",name = "jetCache_",expire = 3600,timeUnit = TimeUnit.SECONDS)
private Cache<String,String> jetCache;
@Override
public void putCache() {
String key = "mike";
jetCache.put(key,"随便存点东西");
}
@Override
public void getCache() {
String key = "mike";
String value = jetCache.get(key);
System.out.println("value = " + value);
}
先调 /put-cache
存数据、再调 /get-cache
取数据,控制台输出如下:
可以看到通过 key
是能够获取之前存放的 value
的,用法和 map 集合类似,这样就实现了通过 JetCache 实现缓存。
比较常用的方法就是put
和 get
方法, Cache
接口的详细 API
可参考官方文档,这里就不多赘述了。
5. 方法缓存的使用
JetCache方法缓存和SpringCache比较类似,它原生提供了TTL支持,以保证最终一致,并且支持二级缓存。
在spring环境下,使用@Cached注解可以为一个方法添加缓存,@CacheUpdate
用于更新缓存,@CacheInvalidate
用于移除缓存元素。注解可以加在接口上也可以加在类上,加注解的类必须是一个spring bean,例如官方文档所举例:
public interface UserService {
@Cached(name="userCache.", key="#userId", expire = 3600)
User getUserById(long userId);
@CacheUpdate(name="userCache.", key="#user.userId", value="#user")
void updateUser(User user);
@CacheInvalidate(name="userCache.", key="#userId")
void deleteUser(long userId);
}
5.1 @Cached 的使用
@Cached
注解和 @CreateCache 的属性非常类似,但是多几个:
属性 | 默认值 | 说明 |
---|---|---|
area | “default” | 如果在配置中配置了多个缓存area,在这里指定使用哪个area |
name | 未定义 | 指定缓存的唯一名称,不是必须的,如果没有指定,会使用类名+方法名。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。 |
key | 未定义 | 使用SpEL指定key,如果没有指定会根据所有参数自动生成。 |
expire | 未定义 | 超时时间。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为无穷大 |
timeUnit | TimeUnit.SECONDS | 指定expire的单位 |
cacheType | CacheType.REMOTE | 缓存的类型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存 |
localLimit | 未定义 | 如果cacheType为LOCAL或BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为100 |
localExpire | 未定义 | 仅当cacheType为BOTH时适用,为内存中的Cache指定一个不一样的超时时间,通常应该小于expire |
serialPolicy | 未定义 | 指定远程缓存的序列化方式。可选值为SerialPolicy.JAVA和SerialPolicy.KRYO。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为SerialPolicy.JAVA |
keyConvertor | 未定义 | 指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,当前支持KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不转换,FASTJSON可以将复杂对象KEY转换成String。如果注解上没有定义,会使用全局配置。 |
enabled | true | 是否激活缓存。例如某个dao方法上加缓存注解,由于某些调用场景下不能有缓存,所以可以设置enabled为false,正常调用不会使用缓存,在需要的地方可使用CacheContext.enableCache在回调中激活缓存,缓存激活的标记在ThreadLocal上,该标记被设置后,所有enable=false的缓存都被激活 |
cacheNullValue | false | 当方法返回值为null的时候是否要缓存 |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true的时候才去缓存中查询 |
postCondition | 未定义 | 使用SpEL指定条件,如果表达式返回true的时候才更新缓存,该评估在方法执行后进行,因此可以访问到#result |
举例:我在我 TestService
中将查询用户信息的接口 queryUserInfo
添加了一个方法注解,缓存名称为 userCache.
,key 是使用 SpEL
指定方法参数,过期时间设置为 3600秒
, 使用本地缓存。
public interface TestService {
@Cached(name="userCache.", key="#req", expire = 3600,cacheType = CacheType.LOCAL)
UserInfoResp queryUserInfo(IdReq req);
}
可在该方法中打断点,可以看到第一次执行该方法会执行方法中的代码,3600秒
内如果用同样的参数去执行该方法是不会经过断点的,那就能够说明是拿了缓存中的数据。
其次,使用方法缓存还有两个需要注意的就是更新和清楚缓存是怎么操作的,JetCache 和 Spring Cache 差不多,也是通过注解去实现的额,分别是:@CacheUpdate
、@CacheInvalidate
5.2 @CacheUpdate 的使用
作用:更新缓存
属性 | 默认值 | 说明 |
---|---|---|
area | “default” | 如果在配置中配置了多个缓存area,在这里指定使用哪个area,指向对应的@Cached定义。 |
name | 未定义 | 指定缓存的唯一名称,指向对应的@Cached定义。 |
key | 未定义 | 使用SpEL指定key |
value | 未定义 | 使用SpEL指定value |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true才执行更新,可访问方法结果#result |
举例:
public interface TestService {
@Cached(name="userCache.", key="#req", expire = 3600,cacheType = CacheType.LOCAL)
UserInfoResp queryUserInfo(IdReq req);
@CacheUpdate(name="userCache.", key="#req", value = "#UserInfoResp")
void saveUserInfo(IdReq req);
}
5.3 @CacheInvalidate 的使用
作用:删除缓存
属性 | 默认值 | 说明 |
---|---|---|
area | “default” | 如果在配置中配置了多个缓存area,在这里指定使用哪个area,指向对应的@Cached定义。 |
name | 未定义 | 指定缓存的唯一名称,指向对应的@Cached定义。 |
key | 未定义 | 使用SpEL指定key |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true才执行更新,可访问方法结果#result |
举例:
public interface TestService {
@Cached(name="userCache.", key="#req", expire = 3600,cacheType = CacheType.LOCAL)
UserInfoResp queryUserInfo(IdReq req);
@CacheUpdate(name="userCache.", key="#req", value = "#UserInfoResp")
void saveUserInfo(IdReq req);
@CacheInvalidate(name="userCache.", key="#req")
void deleteUserInfo(IdReq req);
}
注意:使用@CacheUpdate
和@CacheInvalidate
的时候,相关的缓存操作可能会失败(比如网络IO错误),所以指定缓存的超时时间是非常重要的。
5.4 @CacheRefresh 的使用
作用:自动刷新缓存
属性 | 默认值 | 说明 |
---|---|---|
refresh | 未定义 | 刷新间隔 |
timeUnit | TimeUnit.SECONDS | 时间单位 |
stopRefreshAfterLastAccess | 未定义 | 指定该key多长时间没有访问就停止刷新,如果不指定会一直刷新 |
refreshLockTimeout | 60秒 | 类型为BOTH/REMOTE的缓存刷新时,同时只会有一台服务器在刷新,这台服务器会在远程缓存放置一个分布式锁,此配置指定该锁的超时时间 |
举例:每间隔 10 秒,刷下缓存
public interface TestService {
@Cached(name="userCache.", key="#req", expire = 3600,cacheType = CacheType.LOCAL)
@CacheRefresh(refresh = 10)
UserInfoResp queryUserInfo(IdReq req);
}
5.5 @CachePenetrationProtect 的使用
缓存穿透保护,原理就是当缓存访问未命中的情况下,对并发进行的加载行为进行保护。 当前版本实现的是单JVM内的保护,即同一个JVM中同一个key只有一个线程去加载,其它线程等待结果。
属性表:
属性 | 默认值 | 说明 |
---|---|---|
value | true | 是否开启 |
timeout | 未定义 | 保护时间,默认integer最大值,其他线程等待超时时间,超时后执行方法 |
timeUnit | TimeUnit.SECONDS | 时间单位 |
举例:
public interface TestService {
@Cached(name="userCache.", key="#req", expire = 3600,cacheType = CacheType.LOCAL)
@CachePenetrationProtect
UserInfoResp queryUserInfo(IdReq req);
}
三、踩坑经验
1. 启动报错
我最开始下载 JetCache 依赖坐标使用的是 <version>2.6.0</version>
这个版本的,然后我项目父工程 spring-boot-starter-parent
的版本是 <version>2.1.4.RELEASE</version>
,我配置本地缓存方案启动不会报错,当时配置远程缓存方案启动就报错,这是由于jedis 和spring-boot-starter-data-redis 的 maven 依赖的版本不兼容导致, 是经常会出现的问题。所以出现这种情况就去更换 JetCache 的版本,应该就能解决。
<!-- JetCache 缓存 -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<!-- 更换前 -->
<!--<version>2.6.0</version>-->
<!-- 更换后 -->
<version>2.4.4</version>
</dependency>
2. @CreateCache 注解失效
我的代码如下:定义了一个 Cache
缓存,两个方法,一个是往缓存中存数据,一个是往缓存中取数据,分别对应 /put-cache
、 /get-cache
两个接口,不管调哪个接口,都报 NullPointerException
,yml 配置和启动注解都有,就是注解没有生效。
@CreateCache(area = "default",name = "jetCache_",expire = 3600,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
private Cache<String,String> jetCache;
@Override
public void putCache() {
String key = "mike";
jetCache.put(key,"随便存点东西");
}
@Override
public void getCache() {
String key = "mike";
String value = jetCache.get(key);
System.out.println("value = " + value);
}
这东西也是依赖的版本不兼容导致的,所以我就继续对照 maven 仓库 尝试寻找一个能够兼容 JetCache
的版本。最后找到了 <version>2.5.9</version>
这一版。PS:个人比较菜,只能通过这种比较笨的方法找了~~
<!-- JetCache 缓存 -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<!-- 更换前 -->
<!--<version>2.6.0</version>-->
<!-- 更换后 -->
<!--<version>2.4.4</version>-->
<!--<version>2.6.5</version>-->
<!--<version>2.5.3</version>-->
<version>2.5.9</version>
</dependency>
先调 /put-cache
存数据、再调 /get-cache
取数据,控制台输出如下:
可以看到 @CreateCache
注解已经生效了。
3. @Cached 注解失效
如果你 @CreateCache
这个注解能够正常使用,但是 @Cached
注解却不行,最大可能的原因就是在启动类上没有加 @EnableMethodCache
注解,或者是 @EnableMethodCache
注解中指定的包路径有错误
,请仔细检查。
参考博客:
阿里开源的缓存框架JetCache
JetCache简介以及配置说明
JetCache源码(一)——简介与注解实现
jetcache的@CreateCache注解不生效
Alibaba 开源通用缓存访问框架:JetCache
springboot 环境下 jetcache使用详解