在上一篇文章Redis+Caffeine两级缓存,让访问速度纵享丝滑中,我们介绍了3种整合Caffeine
和Redis
作为两级缓存使用的方法,虽然说能够实现功能,但实现手法还是太粗糙了,并且遗留了一些问题没有处理。本文将在上一篇的基础上,围绕两个方面进行进一步的改造:
-
JSR107
定义了缓存使用规范,spring中提供了基于这个规范的接口,所以我们可以直接使用spring中的接口进行Caffeine
和Redis
两级缓存的整合改造 -
在分布式环境下,如果一台主机的本地缓存进行修改,需要通知其他主机修改本地缓存,解决分布式环境下本地缓存一致性问题
好了,在明确了需要的改进问题后,下面我们开始正式修改。
改造
在上篇文章的v3
版本中,我们使用自定义注解的方式实现了两级缓存通过一个注解管理的功能。本文我们换一种方式,直接通过扩展spring提供的接口来实现这个功能,在进行整合之前,我们需要简单了解一下JSR107
缓存规范。
JSR107 规范
在JSR107
缓存规范中定义了5个核心接口,分别是CachingProvider
,CacheManager
,Cache
, Entry
和Expiry
,参考下面这张图,可以看到除了Entry
和Expiry
以外,从上到下都是一对多的包含关系。
从上面这张图我们可以看出,一个应用可以创建并管理多个CachingProvider
,同样一个CachingProvider
也可以管理多个CacheManager
,缓存管理器CacheManager
中则维护了多个Cache
。
Cache
是一个类似Map
的数据结构,Entry
就是其中存储的每一个key-value
数据对,并且每个Entry
都有一个过期时间Expiry
。而我们在使用spring集成第三方的缓存时,只需要实现Cache
和CacheManager
这两个接口就可以了,下面分别具体来看一下。
Cache
spring中的Cache
接口规范了缓存组件的定义,包含了缓存的各种操作,实现具体缓存操作的管理。例如我们熟悉的RedisCache
、EhCacheCache
等,都实现了这个接口。
在Cache
接口中,定义了get
、put
、evict
、clear
等方法,分别对应缓存的存入、取出、删除、清空操作。不过我们这里不直接使用Cache
接口,上面这张图中的AbstractValueAdaptingCache
是一个抽象类,它已经实现了Cache
接口,是spring在Cache
接口的基础上帮助我们进行了一层封装,所以我们直接继承这个类就可以。
继承AbstractValueAdaptingCache
抽象类后,除了创建Cache
的构造方法外,还需要实现下面的几个方法:
// 在缓存中实际执行查找的操作,父类的get()方法会调用这个方法
protected abstract Object lookup(Object key);
// 通过key获取缓存值,如果没有找到,会调用valueLoader的call()方法
public <T> T get(Object key, Callable<T> valueLoader);
// 将数据放入缓存中
public void put(Object key, Object value);
// 删除缓存
public void evict(Object key);
// 清空缓存中所有数据
public void clear();
// 获取缓存名称,一般在CacheManager创建时指定
String getName();
// 获取实际使用的缓存
Object getNativeCache();
因为要整合RedisTemplate
和Caffeine
的Cache
,所以这些都需要在缓存的构造方法中传入,除此之外构造方法中还需要再传出缓存名称cacheName
,以及在配置文件中实际配置的一些缓存参数。先看一下构造方法的实现:
public class DoubleCache extends AbstractValueAdaptingCache {
private String cacheName;
private RedisTemplate<Object, Object> redisTemplate;
private Cache<Object, Object> caffeineCache;
private DoubleCacheConfig doubleCacheConfig;
protected DoubleCache(boolean allowNullValues) {
super(allowNullValues);
}
public DoubleCache(String cacheName,RedisTemplate<Object, Object> redisTemplate,
Cache<Object, Object> caffeineCache,
DoubleCacheConfig doubleCacheConfig){
super(doubleCacheConfig.getAllowNull());
this.cacheName=cacheName;
this.redisTemplate=redisTemplate;
this.caffeineCache=caffeineCache;
this.doubleCacheConfig=doubleCacheConfig;
}
//...
}
抽象父类的构造方法中只有一个boolean
类型的参数allowNullValues
,表示是否允许缓存对象为null
。除此之外,AbstractValueAdaptingCache
中还定义了两个包装方法来配合这个参数进行使用,分别是toStoreValue
和fromStoreValue
,特殊用途是用于在缓存null
对象时进行包装、以及在获取时进行解析并返回。
我们之后会在