上一篇写到的guava的使用,但是扩展起来需要入侵业务代码不是很方便
guava实现多级缓存(1)
这里抽象了一层,需要扩展直接继承就好了,直接贴代码
接口
import java.util.Map;
/**
* wxy
* @param <K>
* @param <V>
*/
public interface ILocalCache <K, V> {
/**
* 从缓存中获取数据
* @param key
* @return value
*/
public V get(K key, Map<String,Object> args);
}
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author wxy
* @ClassName: GuavaAbstractLoadingCache
*/
@Data
public abstract class GuavaAbstractLoadingCache<K, V> {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
//用于初始化cache的参数及其缺省值
private int maximumSize = 1000; //最大缓存条数,子类在构造方法中调用setMaximumSize(int size)来更改
private long expireAfterAccess = 10L; //数据存在时长,子类在构造方法中调用setExpireAfterWriteDuration(int duration)来更改
private TimeUnit timeUnit = TimeUnit.MINUTES; //时间单位(分钟)
private LoadingCache<K, V> cache;
/**
* 通过调用getCache().get(key)来获取数据
* @param args 查询条件from Db
* @return cache
*/
public LoadingCache<K, V> getCache(Map<String,Object> args) {
if (cache == null) { //使用双重校验锁保证只有一个cache实例
synchronized (this) {
if (cache == null) {
cache = CacheBuilder.newBuilder().maximumSize(maximumSize) //缓存数据的最大条目,也可以使用.maximumWeight(weight)代替
.expireAfterWrite(expireAfterAccess, timeUnit) //数据被创建多久后被移除
.build(CacheLoader.asyncReloading(new CacheLoader<K, V>() {
@Override
public V load(@NonNull K k) {
return fetchData(k,args);
}
}, initPool()));
logger.info("本地缓存{}初始化成功", this.getClass().getSimpleName());
}
}
}
return cache;
}
/**
* 初始化线程池
*
* @return
*/
protected abstract ExecutorService initPool();
/**
* 根据key从数据库或其他数据源中获取一个value,并被自动保存到缓存中。
*
* @param key
* @return value, 连同key一起被加载到缓存中的。
*/
private V fetchData(K key,Map<String,Object> args) {
V v = null;
//1. redis中读取
v = fetchDataFromRedis(key);
if (v == null) {
//2.数据库中读取
v = fetchDataFromDb(key,args);
//todo 集合是否有影响
if (v != null) {
setRedis(key, v);
}
}
return v;
}
/**
* 数据库中加载
*
* @param key
* @return
*/
protected abstract V fetchDataFromDb(K key,Map<String,Object> args);
/**
* redis中加载
*
* @param key
* @return
*/
protected abstract V fetchDataFromRedis(K key);
/**
* set进redis
*
* @param key
* @param v
*/
protected abstract void setRedis(K key, V v);
/**
* 从缓存中获取数据(第一次自动调用fetchData从外部获取数据),并处理异常
*
* @param key
* @return Value
* @throws ExecutionException
*/
protected V getValue(K key, Map<String,Object> args) throws ExecutionException {
//从guava中获取
V result = getCache(args).get(key);
if (result == null) {
return fetchData(key,args);
}
return result;
}
/**
* 构建key
* @param args
* @return
*/
public abstract String buildKey(Map<String,Object> args);
}
实现类:
import cn.jmfen.sport.cache.GuavaAbstractLoadingCache;
import cn.jmfen.sport.cache.ILocalCache;
import cn.jmfen.sport.constant.Separator;
import cn.jmfen.sport.entity.KeepAnalyzeResult;
import cn.jmfen.sport.entity.StatisticsKeepQuery;
import cn.jmfen.sport.service.IkeepStatisticsService;
import com.central.common.redis.util.RedisUtil;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Auther: wxy
* @Date: 2020/6/23 18:41
* @Description:
*/
@Component
public class KeepAnalyzeResultCache extends GuavaAbstractLoadingCache<String, KeepAnalyzeResult>
implements ILocalCache<String, KeepAnalyzeResult> {
@Autowired
private IkeepStatisticsService ikeepStatisticsService;
@Autowired
private RedisUtil redisUtil;
public KeepAnalyzeResultCache() {
setExpireAfterAccess(20L);
setTimeUnit(TimeUnit.MINUTES);
}
@Override
protected ExecutorService initPool() {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("留存分析缓存-pool-%d").build();
return new ThreadPoolExecutor(2, 5,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10), threadFactory, new ThreadPoolExecutor.AbortPolicy());
}
/**
* 调用数据库
* @param key
* @param args
* @return
*/
@Override
protected KeepAnalyzeResult fetchDataFromDb(String key, Map<String, Object> args) {
StatisticsKeepQuery query = new StatisticsKeepQuery();
Object q = args.get("query");
if (q != null) {
query = (StatisticsKeepQuery) q;
}
//业务层代码
return ikeepStatisticsService.keepQueryV2(query);
}
/**
* 从redis读取
* @param key
* @return
*/
@Override
protected KeepAnalyzeResult fetchDataFromRedis(String key) {
return (KeepAnalyzeResult) redisUtil.get(key);
}
@Override
protected void setRedis(String key, KeepAnalyzeResult keepAnalyzeResult) {
//redis缓存15分钟
redisUtil.set(key, keepAnalyzeResult, 60 * 15);
}
@Override
public String buildKey(Map<String, Object> args) {
Object o = args.get("prefix");
Assert.notNull(o, "参数prefix不能为空");
String prefix = (String) o;
Assert.hasLength(prefix, "参数prefix不能为空");
StatisticsKeepQuery query = null;
Object q = args.get("query");
if (q != null) {
query = (StatisticsKeepQuery) q;
}
StringBuilder sb = new StringBuilder(prefix);
if (query != null) {
String channel = query.getChannel();
if (StringUtils.isNotBlank(channel)) {
sb.append(Separator.COLON).append(channel);
}
String endTime = query.getEndTime();
if (StringUtils.isNotBlank(endTime)) {
sb.append(Separator.COLON).append(endTime, 0, 10);
}
String startTime = query.getStartTime();
if (StringUtils.isNotBlank(startTime)) {
sb.append(Separator.COLON).append(startTime, 0, 10);
}
//types 由platform映射来
List<String> types = query.getTypes();
if (CollectionUtils.isNotEmpty(types)) {
Collections.sort(types);
sb.append(Separator.COLON).append(types);
}
}
String key = sb.toString();
logger.info("留存分析缓存,key {}", key);
return key;
}
@Override
public KeepAnalyzeResult get(String key, Map<String, Object> args) {
Assert.hasLength(key, "缓存key不能为空");
try {
Object value = getValue(key, args);
return (KeepAnalyzeResult) value;
} catch (Exception e) {
logger.error("无法根据baseDataKey={}获取留存分析缓存,可能是数据库中无该记录。", key, e);
return null;
}
}
}
controller:
@Autowired
private KeepAnalyzeResultCache keepAnalyzeResultCache;
/**
* 留存分析(使用中)
* @param query
* @return
*/
@GetMapping("keep")
public Result<KeepAnalyzeResult> keepQueryV2(StatisticsKeepQuery query) {
Map<String, Object> args = new HashMap<>();
args.put("prefix",CacheKey.KEEP_ANALYZE_PREFIX);
args.put("query",query);
return Result.data(keepAnalyzeResultCache.get(keepAnalyzeResultCache.buildKey(args),args));
//return Result.data(ikeepStatisticsService.keepQueryV2(query));
}