实现思路:
1) 采用caffeine作为一级缓存,caffeine是一个高性能的Java缓存库,采用的是Window TinyLfu回收策略,
提供了一个近乎最佳的缓存命中率;优点是数据就在应用内存中所以速度块;缺点是1)受应用内存限制,容量有限,
2)没有持久化,重启服务后缓存数据会丢失 3)分布式式环境下,缓存数据无法同步
2) 采用redis作为二级缓存,redis是一个高可用高性能的key-value数据库,支持多种数据类型,支持集群,
和应用服务器分开部署易于横向扩展;优点是 1) 支持多种数据类型,易于扩容 2)有持久化,重启应用服务器缓存数据
不会丢失 3) redis是一个集中式缓存,不存在和应用服务器之间数据同步的问题; 缺点是每次都需要访问redis,存在
IO浪费情况
总结:从上面的描述可以看出Caffeine和redis的优缺点正好相反,所以他们可以有效的互补。
实现步骤:
1) 基本架构
1.1 lombok 敏捷开发
1.2 log4j 日志
1.3) fastJson 和 jackson
2) 定义缓存的顶级接口
package org.github.roger.cache;
import java.util.concurrent.Callable;
/**
* 缓存的顶级接口
*/
public interface ICache {
/**
* @return 返回缓存的名称
*/
String getName();
/**
* @return 返回真实的缓存对象
*/
Object getRealCache();
/**
* 根据key获取其对应的缓存对象,如果没有就返回null
* @param key
* @return 返回缓存key对应的缓存对象
*/
Object get(Object key);
/**
* 根据key获取其对应的缓存对象,并将返回的缓存对象转换成对应的类型
* 如果没有就返回null
* @param key
* @param type 缓存对象的类型
* @param <T>
* @return
*/
<T> T get(Object key,Class<T> type);
/**
* 根据key获取其对应的缓存对象,并将返回的缓存对象转换成对应的类型
* 如果对应key不存在则调用valueLoader加载数据
* @param key
* @param valueLoader 加载缓存的回调方法
* @param <T>
* @return
*/
<T> T get(Object key, Callable<T> valueLoader);
/**
* 将对应key-value放到缓存,如果key原来有值就直接覆盖
*
* @param key 缓存key
* @param value 缓存的值
*/
void put(Object key, Object value);
/**
* 如果缓存key没有对应的值就将值put到缓存,如果有就直接返回原有的值
* 就相当于:
* Object existingValue = cache.get(key);
* if (existingValue == null) {
* cache.put(key, value);
* return null;
* } else {
* return existingValue;
* }
* @param key 缓存key
* @param value 缓存key对应的值
* @return 因为值本身可能为NULL,或者缓存key本来就没有对应值的时候也为NULL,
* 所以如果返回NULL就表示已经将key-value键值对放到了缓存中
*/
Object putIfAbsent(Object key, Object value);
/**
* 在缓存中删除对应的key
*
* @param key 缓存key
*/
void evict(Object key);
/**
* 清楚缓存
*/
void clear();
}
3.定义一个缓存顶级接口的抽象实现类,用于实现不同级别的缓存的公共方法
package org.github.roger.cache;
import com.alibaba.fastjson.JSON;
import org.github.roger.support.NullValue;
import org.springframework.util.Assert;
import java.util.concurrent.Callable;
public abstract class AbstractValueAdaptingCache implements ICache{
//缓存名称
private String name;
public AbstractValueAdaptingCache(String name) {
Assert.notNull(name,"缓存名称不能为空");
this.name = name;
}
/**
* 缓存对象是否允许为空
* @return true 允许 false 不允许
*/
public abstract boolean isAllowNullValues();
public String getName() {
return this.name;
}
public <T> T get(Object key, Class<T> type) {
return (T) fromStoreValue(get(key));
}
protected Object fromStoreValue(Object storeValue) {
if(isAllowNullValues() && storeValue instanceof NullValue){
return null;
}
return storeValue;
}
protected Object toStoreValue(Object userValue){
if(isAllowNullValues() && userValue == null){
return NullValue.INSTANCE;
}
return userValue;
}
/**
* {@link #get(Object, Callable)} 方法加载缓存值的包装异常
*/
public class LoaderCacheValueException extends RuntimeException {
private final Object key;
public LoaderCacheValueException(Object key, Throwable ex) {
super(String.format("加载key为 %s 的缓存数据,执行被缓存方法异常", JSON.toJSONString(key)), ex);
this.key = key;
}
public Object getKey() {
return this.key;
}
}
}
4.创建一个多级模式缓存的类,继承上面的抽象类
4.1) 对每个级别的缓存创建配置项类,以及多级配置项类
package org.github.roger.settings;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.github.roger.enumeration.ExpireMode;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class FirstCacheSetting implements Serializable {
/**
* 缓存初始Size
*/
private int initialCapacity = 10;
/**
* 缓存最大Size
*/
private int maximumSize = 500;
/**
* 缓存有效时间
*/
private int expireTime = 0;
/**
* 缓存时间单位
*/
private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
/**
* 缓存失效模式{@link ExpireMode}
*/
private ExpireMode expireMode = ExpireMode.WRITE;
public boolean isAllowNullValues() {
return false;
}
}
package org.github.roger.settings;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.github.roger.enumeration.ExpireMode;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class SecondaryCacheSetting implements Serializable {
/**
* 缓存有效时间
*/
private long expiration = 0;
/**
* 缓存主动在失效前强制刷新缓存的时间
*/
private long preloadTime = 0;
/**
* 时间单位 {@link TimeUnit}
*/
private TimeUnit timeUnit = TimeUnit.MICROSECONDS;
/**
* 是否强制刷新(走数据库),默认是false
*/
private boolean forceRefresh = false;
/**
* 是否使用缓存名称作为 redis key 前缀
*/
private boolean usePrefix = true;
/**
* 是否允许存NULL值
*/
boolean allowNullValue = false;
/**
* 非空值和null值之间的时间倍率,默认是1。allowNullValue=true才有效
*
* 如配置缓存的有效时间是200秒,倍率这设置成10,
* 那么当缓存value为null时,缓存的有效时间将是20秒,非空时为200秒
*/
int magnification = 1;
}
package org.github.roger.settings;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class MultiLayeringCacheSetting implements Serializable {
private static final String SPLIT = "-";
/**
* 内部缓存名,由[一级缓存有效时间-二级缓存有效时间-二级缓存自动刷新时间]组成
*/
private String internalKey;
boolean useFirstCache = true;
private FirstCacheSetting firstCacheSetting;
private SecondaryCacheSetting secondaryCacheSetting;
public MultiLayeringCacheSetting(FirstCacheSetting firstCacheSetting, SecondaryCacheSetting secondaryCacheSetting) {
this.firstCacheSetting = firstCacheSetting;
this.secondaryCacheSetting = secondaryCacheSetting;
internalKey();
}
@JSONField(serialize = false, deserialize = false)
private void internalKey() {
// 一级缓存有效时间-二级缓存有效时间-二级缓存自动刷新时间
StringBuilder sb = new StringBuilder();
if (firstCacheSetting != null) {
sb.append(firstCacheSetting.getTimeUnit().toMillis(firstCacheSetting.getExpireTime()));
}
sb.append(SPLIT);
if (secondaryCacheSetting != null) {
sb.append(secondaryCacheSetting.getTimeUnit().toMillis(secondaryCacheSetting.getExpiration()));
sb.append(SPLIT);
sb.append(secondaryCacheSetting.getTimeUnit().toMillis(secondaryCacheSetting.getPreloadTime()));
}
internalKey = sb.toString();
}
}
4.2) 利用配置项类和步骤3创建的缓存顶级接口的抽象实现类 创建的创建多级模式缓存的类
package org.github.roger;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.github.roger.cache.AbstractValueAdaptingCache;
import org.github.roger.cache.ICache;
import org.github.roger.settings.MultiLayeringCacheSetting;
import java.util.concurrent.Callable;
@Slf4j
public class MultiLayeringCache extends AbstractValueAdaptingCache {
private AbstractValueAdaptingCache firstCache;
private AbstractValueAdaptingCache secondCache;
private boolean useFirstCache = true;
private MultiLayeringCacheSetting multilayeringCacheSetting;
public MultiLayeringCache(String name, AbstractValueAdaptingCache firstCache, AbstractValueAdaptingCache secondCache, boolean useFirstCache,MultiLayeringCacheSetting multilayeringCacheSetting) {
super(name);
this.firstCache = firstCache;
this.secondCache = secondCache;
this.useFirstCache = useFirstCache;
this.multilayeringCacheSetting = multilayeringCacheSetting;
}
public Object getRealCache() {
return this;
}
public Object get(Object key) {
Object storeValue = null;
if(useFirstCache){
storeValue = firstCache.get(key);
log.info("查询一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(storeValue));
}
if(storeValue == null){
storeValue = secondCache.get(key);
firstCache.putIfAbsent(key, storeValue);
log.info("查询二级缓存,并将数据放到一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(storeValue));
}
return fromStoreValue(storeValue);
}
@Override
public <T> T get(Object key, Class<T> type) {
if (useFirstCache) {
Object result = firstCache.get(key, type);
log.info("查询一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(result));
if (result != null) {
return (T) fromStoreValue(result);
}
}
T result = secondCache.get(key, type);
firstCache.putIfAbsent(key, result);
log.info("查询二级缓存,并将数据放到一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(result));
return result;
}
public <T> T get(Object key, Callable<T> valueLoader) {
if (useFirstCache) {
Object result = firstCache.get(key);
log.info("查询一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(result));
if (result != null) {
return (T) fromStoreValue(result);
}
}
T result = secondCache.get(key, valueLoader);
firstCache.putIfAbsent(key, result);
log.info("查询二级缓存,并将数据放到一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(result));
return result;
}
public void put(Object key, Object value) {
secondCache.put(key, value);
// 删除一级缓存
if (useFirstCache) {
//TODO deleteFirstCache(key);
}
}
public Object putIfAbsent(Object key, Object value) {
Object result = secondCache.putIfAbsent(key, value);
// 删除一级缓存
if (useFirstCache) {
//TODO deleteFirstCache(key);
}
return result;
}
public void evict(Object key) {
// 删除的时候要先删除二级缓存再删除一级缓存,否则有并发问题
secondCache.evict(key);
// 删除一级缓存
if (useFirstCache) {
//TODO deleteFirstCache(key);
}
}
public void clear() {
// 删除的时候要先删除二级缓存再删除一级缓存,否则有并发问题
secondCache.clear();
if (useFirstCache) {
//TODO
/*// 清除一级缓存需要用到redis的订阅/发布模式,否则集群中其他服服务器节点的一级缓存数据无法删除
RedisPubSubMessage message = new RedisPubSubMessage();
message.setCacheName(getName());
message.setMessageType(RedisPubSubMessageType.CLEAR);
// 发布消息
RedisPublisher.publisher(redisTemplate, new ChannelTopic(getName()), message);*/
}
}
public ICache getFirstCache() {
return firstCache;
}
public ICache getSecondCache() {
return secondCache;
}
public boolean isAllowNullValues() {
return secondCache.isAllowNullValues();
}
}