1、配置

1.1、POM

  • springboot版本:2.6.6
  • 特别要注意jar包的版本,如果跑不起来,基本上都是版本问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-spring-boot2-starter</artifactId>
<version>2.8.0-release</version>
</dependency>
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-core</artifactId>
<version>2.8.0-release</version>
</dependency>
<!-- Jedis依赖(使用低版本,否则J2cache 2.8.4依赖3.7.1版本不兼容,RedisClient类编译不通过)-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.1</version>
</dependency>

1.2、配置文件

1.2.1、application.properties

1
2
#环境配置
spring.profiles.active=test

1.2.2、application-test.properties

  • 注意J2cache配置文件位置

  • redis客户端类型:可以选择 lettuce和jedis:

      • net.oschina.j2cache.autoconfigure.J2CacheSpringRedisAutoConfiguration默认使用lettuce
  • lettuce底层使用Netty框架,性能好

      • 稳定,redis api全面
  • 较低版本的将j2cache使用的jedis客户端

  • 二级缓存序列化格式,采用了线上的fst,测试的时候可以试试使用fastjson,这样可以在redis中看到存储对象中的内容。net.oschina.j2cache.util.SerializationUtils中支持一下几种序列化:

    • fst:org.nustaq.serialization.FSTConfiguration,效率貌似较高,线上使用中。
  • kryo:com.esotericsoftware.kryo.io.Input、com.esotericsoftware.kryo.io.Output

  • json:org.nustaq.serialization.FSTConfiguration,使用 FST 的 JSON 对象序列化

  • fastjson:com.alibaba.fastjson.JSON

  • java:标准的 Java 序列化,使用ObjectOutputStream、ObjectInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# j2cache配置文件位置(默认/j2cache.properties)
j2cache.config-location=classpath:j2cache/j2cache-${spring.profiles.active}.properties
# 是否启用Spring Cache(默认false)
j2cache.open-spring-cache=true
# 设置Spring Cache是否允许缓存null值(默认false)
j2cache.allow-null-values=true
# 是否开启2级缓存(默认true)
j2cache.l2-cache-open=true
# redis客户端类型(jedis | lettuce(默认))
j2cache.redis-client=jedis
# 缓存清除模式
# active:主动清除,二级缓存过期主动通知各节点清除,优点在于所有节点可以同时收到缓存清除
# passive:被动清除(默认),一级缓存过期进行通知各节点清除一二级缓存
# blend:两种模式一起运作,对于各个节点缓存准确性以及及时性要求高的可以使用(推荐使用前面两种模式中一种)
j2cache.cache-clean-mode=active
# 二级缓存序列化格式(fst(推荐) | kryo | json | fastjson | java(默认) | 自定义classname)
j2cache.j2CacheConfig.serialization=fst
# Spring Cache缓存类型(generic | jcache | ehcache | hazelcast | infinispan | couchbase | redis | caffeine | simple | none)
# generic即使用自定义的Cache Beans,对应J2CacheCacheManager实现
spring.cache.type=GENERIC

1.2.3、j2cache/j2cache-test.properties

  • 一级缓存可以选择caffeine或者ehcache

      • Java8高性能缓存库
  • j2cache目前仅支持配置设置最多缓存对象数量及过期时间

      • EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。
  • EhCache 2.x与EhCache 3.x

  • 目前线上使用EhCache 2.x,需配置ehcache.xml使用

  • 注意Redis连接配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#########################################
# J2Cache全局配置
#########################################
# 缓存provider名称
# L1: caffeine | ehcache | ehcache3 |
# L2: redis | lettuce | readonly-redis | memcached
# 其他: none | 自定义classname
# 开启1级缓存Caffeine
j2cache.L1.provider_class = ehcache
# 开启2级缓存Redis - SpringBoot Redis自定义实现
j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
# 查看源码发现j2cache-spring-boot2-starter中J2CacheSpringRedisAutoConfiguration使用以一套配置属性(匹配J2cache redis属性)
# 所以此处jedis和lettuce可以使用同一套配置redis.*
j2cache.L2.config_section = redis
# 是否启用同步一级缓存的Time-To-Live超时时间到Redis TTL(true启用,false不启用则永不超时)
j2cache.sync_ttl_to_redis = true
# 缓存变更广播方式(jgroups | redis | lettuce | rabbitmq | rocketmq | none | 自定义class)
# 启用缓存变更广播redis实现 - SpringBoot Redis自定义实现
j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
# Whether to cache null objects by default (default false)
j2cache.default_cache_null_object = true
# 二级缓存序列化格式(fst(推荐) | kryo | json | fastjson | java(默认) | fse | 自定义classname)
# 使用application.yml中配置
j2cache.serialization = ${j2cache.j2CacheConfig.serialization}


#########################################
# L1 Caffeine配置
#########################################
# caffeine缓存配置
# 格式如下,目前仅支持配置设置cache中最多缓存对象数量及过期时间
# caffeine.region.[regionName] = size, xxxx[s|m|h|d]
# default为默认配置
# caffeine.region.default = 100,5m
# 其他为各自region的自定义配置
# caffeine.region.myJ2CacheRegion = 10,5m

#########################################
# L1 ehcache配置
#########################################
ehcache.configXml=/ehcache.xml

#########################################
# L2 Redis(Jedis)配置
#########################################
# Redis集群模式(single | sentinel | cluster | sharded)
redis.mode = single
# Redis存储模式
# generic: 支持key过期,使用key/value存储
# hash: 不支持key过期,使用hash存储
redis.storage = generic
# Redis发布订阅(Pub/Sub)对应的channel名称
redis.channel = j2cache
# Redis缓存命名空间,可选(默认空)
# key格式 = namespace:region:key
redis.namespace = myJ2Cache
# redis command scan parameter count, default[1000]
#redis.scanCount = 1000

########### Redis连接配置 ###############
# 多节点使用半角逗号分隔,例如: 192.168.0.10:6379,192.168.0.11:6379,192.168.0.12:6379
redis.hosts = 192.168.0.10:6379
redis.timeout = 2000
redis.password = xxxxxx
redis.database = 10
redis.ssl = false

########### Redis连接池配置 ###############
# 最大连接数(获取连接时进行判断,也即连接数的上限)
redis.maxTotal = 100
# 最大空闲连接数(在归还连接时判断,若当前连接数超过maxIdle连接数,则释放归还的连接)
redis.maxIdle = 10

# 最小空闲连接数(超过maxIdle则使用maxIdle值,单独的Evict任务负责清理超时的连接)
redis.minIdle = 10
# 驱逐空闲连接的最小间隔时间
redis.minEvictableIdleTimeMillis = 60000
# minEvictableIdleTimeMillis为正数则覆盖softMinEvictableIdleTimeMillis 配置
redis.softMinEvictableIdleTimeMillis = 10
# 驱逐空闲连接的Evict任务运行时间间隔
redis.timeBetweenEvictionRunsMillis = 300000
# 单次Evict清理任务的测试连接数据(即仅对指定数据量的连接进行测试,并将无效连接释放)
redis.numTestsPerEvictionRun = 10

# 获取连接的最大等待时长ms(当连接池已耗尽时)
redis.maxWaitMillis = 5000
# 当资源池耗尽,获取连接时是否需要等待
redis.blockWhenExhausted = true

# 获取资源后进先出(false则对应先进先出)
redis.lifo = false
# 是否测试连接可用
redis.testOnBorrow = false
redis.testOnReturn = false
redis.testWhileIdle = true
redis.jmxEnabled = true

1.2.4、ehcache.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2017. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
~ Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
~ Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
~ Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
~ Vestibulum commodo. Ut rhoncus gravida arcu.
-->

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!--<diskStore path="java.io.tmpdir"/>-->
<diskStore path="user.home"/>


<defaultCache maxElementsInMemory="500" eternal="false" timeToIdleSeconds="60" timeToLiveSeconds="60"
overflowToDisk="false">
</defaultCache>
<!--
配置自定义缓存
maxElementsInMemory:缓存中允许创建的最大对象数
eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,
两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,
如果该值是 0 就意味着元素可以停顿无穷长的时间。
timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,
这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
overflowToDisk:内存不足时,是否启用磁盘缓存。
memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。
-->
<cache name="myJ2CacheRegion"
maxElementsInMemory="10"
eternal="false"
timeToLiveSeconds="1800"
overflowToDisk="false"
memoryStoreEvictionPolicy="LFU">
</cache>
</ehcache>

2、使用

2.1、测试代码

2.1.1、缓存的对象User

  • 注意要实现序列化接口
1
2
3
4
5
6
7
8
9
10
11
12
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = -7361267310378825384L;
private BigInteger id;
private String name;
private Integer num;
private Date date;
private LocalDateTime localDateTime;
}

2.2.2、UserService操作缓存

  • 注意UserService交由Spring管理
  • 可以使用三种方式操作缓存:注解、CacheChannel、CacheManager

2.2.2.1、注解

  • @Cacheable
  • @CacheEvict
  • @CachePut
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
@Slf4j
public class UserService {
private final AtomicInteger num = new AtomicInteger(0);
@Cacheable(value = "myJ2CacheRegion", key = "#id")
public User getUserByAnnotation(BigInteger id) {
User user = User.builder()
.id(id)
.name("user_" + id)
.num(num.incrementAndGet())
.date(new Date())
.localDateTime(LocalDateTime.now())
.build();
log.info("getUserByAnnotation: {}", JSON.toJSONString(user));
return user;
}
@CacheEvict(value = "myJ2CacheRegion", key = "#user.id")
public void cacheEvictByAnnotation(User user) {
}
@CachePut(value = "myJ2CacheRegion", key = "#user.id")
public User cachePutByAnnotation(User user) {
user.setDate(new Date());
user.setLocalDateTime(LocalDateTime.now());
log.info("cachePutByAnnotation: {}", user);
return user;
}
@CacheEvict(value = "myJ2CacheRegion", allEntries = true)
public void clearByAnnotation() {
}
}

2.2.2.2、CacheChannel

  • net.oschina.j2cache.CacheChannel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Service
@Slf4j
public class UserService {
/**
* J2Cache依赖
* net.oschina.j2cache.CacheChannel
*/
@Autowired
private CacheChannel cacheChannel;
private final AtomicInteger num = new AtomicInteger(0);
public User getUserByCacheChannel(BigInteger id) {
CacheObject cacheObject = cacheChannel.get("myJ2CacheRegion", String.valueOf(id));
if (cacheObject == null) {
return null;
}
User user = (User) cacheObject.getValue();
log.info("getUserByCacheChannel: {}", JSON.toJSONString(user));
return user;
}
public void cacheEvictByCacheChannel(User user) {
cacheChannel.evict("myJ2CacheRegion", String.valueOf(user.getId()));
}
public void cachePutByCacheChannel(User user) {
user.setNum(num.incrementAndGet());
user.setDate(new Date());
user.setLocalDateTime(LocalDateTime.now());
cacheChannel.set("myJ2CacheRegion", String.valueOf(user.getId()), user);
log.info("cachePutByCacheChannel: {}", user);
}
public void clearByCacheChannel() {
cacheChannel.clear("myJ2CacheRegion");
}
}

2.2.2.3、CacheManager

  • org.springframework.cache.CacheManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Service
@Slf4j
public class UserService {
/**
* Spring CacheManager依赖
* org.springframework.cache.CacheManager
*/
@Autowired
private CacheManager cacheManager;
private final AtomicInteger num = new AtomicInteger(0);
public User getUserByCacheManager(BigInteger id) {
Cache cache = cacheManager.getCache("myJ2CacheRegion");
if (cache == null) {
return null;
}
User user = cache.get(id, User.class);
log.info("getUserByCacheManager: {}", JSON.toJSONString(user));
return user;
}
public void cacheEvictByCacheManager(User user) {
Cache cache = cacheManager.getCache("myJ2CacheRegion");
if (cache == null) {
return;
}
cache.evict(user.getId());
}
public void cachePutByCacheManager(User user) {
user.setNum(num.incrementAndGet());
user.setDate(new Date());
user.setLocalDateTime(LocalDateTime.now());
Cache cache = cacheManager.getCache("myJ2CacheRegion");
if (cache == null) {
return;
}
cache.put(user.getId(), user);
log.info("cachePutByCacheManager: {}", user);
}
public void clearByCacheManager() {
Cache cache = cacheManager.getCache("myJ2CacheRegion");
if (cache == null) {
return;
}
cache.clear();
}
}

2.2、简单介绍

2.2.1、spring缓存注解

  • 常用注解有三个:@Cacheable、@CacheEvict、@CachePut
  • 位于spring-context包下

2.2.1.1、@Cacheable

Annotation indicating that the result of invoking a method (or all methods in a class) can be cached.

  • 首先从缓存获取,存在则返回,如果不存在,执行方法,并将方法返回结果写入缓存,并返回。

  • 常用的属性value、key、condition、unless

  • 可以使用spEL表达式

1
2
//在id不为空切
@Cacheable(value = "myJ2CacheRegion", key = "'getUserByAnnotation_'+#id", condition = "#id!=null", unless = "#result==null")

注意:key如果是常量必须用单引号引起来,否则会抛异常:

EL1008E: Property or field ‘xxxx’ cannot be found on object of type ‘org.springframework.cache.interceptor.CacheExpressionRootObject’ - maybe not public

常用spEL表达式:

img

2.2.1.2、@CacheEvict

Annotation indicating that a method (or all methods on a class) triggers a {@link org.springframework.cache.Cache#evict(Object) cache evict} operation.

  • 清除缓存操作,常用参数为:value和key
  • 可以使用spEL
1
@CacheEvict(value = "myJ2CacheRegion", key = "#user.id")

2.2.1.3、@CachePut

Annotation indicating that a method (or all methods on a class) triggers a
* {
@link* org.springframework.cache.Cache#put(Object, Object) cache put} operation.

  • 与@Cacheable 使用方法类似,也是将方法的返回结果放入缓存。
  • 每次执行都只更新缓存,并不会读取缓存。

2.2.2、CacheChannel

2.2.3、CacheManager

  • org.springframework.cache.CacheManager
  • spring提供的通用缓存管理SPI
  • j2cache通过net.oschina.j2cache.cache.support.J2CacheCacheManger实现了CacheManager接口,并在J2CacheSpringCacheAutoConfiguration类中注入spring。
  • J2CacheCacheManger底层操作的j2cache的CacheChannel

参考

红薯 / J2Cache

罗小爬 / j2cache-demo

SpringBoot 缓存之 @Cacheable 详细介绍[