毕竟西湖六月中,风光不与四时同。java
接天莲叶无穷碧,映日荷花别样红。git
晓出净慈寺送林子方-杨万里redis
周末与小伙伴约了一波西湖,这个时间荷花开的正好...,在开始文章以前先放一张“佛系”美图来镇楼!!!数据库
最近这段时间用了下谷歌的guava,本身封了一个缓存模板方案,特此记录,以备后续所需。缓存
为何要从这个来讲起,由于不说这个就没guava什么事了!数据结构
最近项目中须要使用缓存来对一查查询频繁的数据作缓存处理;首先咱们也不但愿引入三方的如redis或者memcache这样的服务进来,其次是咱们对于数据一致性的要求并非很高,不须要集群内的查询接口共享到一份缓存数据;因此这样一来咱们只要实现一个基于内存的缓存便可。app
最开始我并无考虑使用guava来作这个事情,而是本身写了一套基于CurrentHashMap的缓存方案;这里须要明确一点,由于缓存在这个场景里面但愿提供超时清除的能力,而基于因此在本身缓存框架中增长了定时清除过时数据的能力。框架
这里我就直接把定时清楚的这段代码放上来:ide
/** * 静态内部类来进行超时处理 */
private class ClearCacheThread extends Thread {
@Override
public void run() {
while (true){
try {
long now = System.currentTimeMillis();
Object[] keys = map.keySet().toArray();
for (Object key : keys) {
CacheEntry entry = map.get(key);
if (now - entry.time >= cacheTimeout) {
synchronized (map) {
map.remove(key);
if (LOGGER.isDebugEnabled()){
LOGGER.debug("language cache timeout clear");
}
}
}
}
}catch (Exception e){
LOGGER.error("clear out time cache value error;",e);
}
}
}
}
复制代码
这个线程是用来单独处理过时数据的。缓存初始化时就会触发这个线程的start方法开始执行。函数
正式因为这段代码的不合理致使我在发布dev环境以后,机器GC触发的频次高的离谱。在尝试了不一样的修复方案以后,最后选择放弃了;改用guava了!
小伙伴们能够在下面留言来讨论下这里为何会存在频繁GC的问题;我会把结论放在评论回复里面。
为何选用guava呢,很显然,是大佬推荐的!!!
guava是谷歌提供的一个基于内存的缓存工具包,Guava Cache 提供了一种把数据(key-value对)缓存到本地(JVM)内存中的机制,适用于不多会改动的数据。Guava Cache 与 ConcurrentMap 很类似,但也不彻底同样。最基本的区别是 ConcurrentMap 会一直保存全部添加的元素,直到显式地移除。相对地,Guava Cache 为了限制内存占用,一般都设定为自动回收元素。
对于咱们的场景,guava 提供的能力知足了咱们的须要:
既然选择它了,咱们仍是有必要来先对它有个大体的了解;先来看看它提供的一些类和接口:
接口/类 | 详细解释 |
---|---|
Cache | 【I】;定义get、put、invalidate等操做,这里只有缓存增删改的操做,没有数据加载的操做。 |
AbstractCache | 【C】;实现Cache接口。其中批量操做都是循环执行单次行为,而单次行为都没有具体定义。 |
LoadingCache | 【I】;继承自Cache。定义get、getUnchecked、getAll等操做,这些操做都会从数据源load数据。 |
AbstractLoadingCache | 【C】;继承自AbstractCache,实现LoadingCache接口。 |
LocalCache | 【C】;整个guava cache的核心类,包含了guava cache的数据结构以及基本的缓存的操做方法。 |
LocalManualCache | 【C】;LocalCache内部静态类,实现Cache接口。其内部的增删改缓存操做所有调用成员变量localCache(LocalCache类型)的相应方法。 |
LocalLoadingCache | 【C】;LocalCache内部静态类,继承自LocalManualCache类,实现LoadingCache接口。其全部操做也是调用成员变量localCache(LocalCache类型)的相应方法 |
CacheBuilder | 【C】;缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。CacheBuilder在build方法中,会把前面设置的参数,所有传递给LocalCache,它本身实际不参与任何计算 |
CacheLoader | 【C】;用于从数据源加载数据,定义load、reload、loadAll等操做。 |
整个来看的话,guava里面最核心的应该算是 LocalCache 这个类了。
@GwtCompatible(emulated = true)
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> 复制代码
关于这个类的源码这里就不细说了,直接来看下在实际应用中个人封装思路【封装知足我当前的需求,若是有小伙伴须要借鉴,能够本身在作扩展】
private static final int MAX_SIZE = 1000;
private static final int EXPIRE_TIME = 10;
private static final int DEFAULT_SIZE = 100;
private int maxSize = MAX_SIZE;
private int expireTime = EXPIRE_TIME;
/** 时间单位(分钟) */
private TimeUnit timeUnit = TimeUnit.MINUTES;
/** Cache初始化或被重置的时间 */
private Date resetTime;
/** 分别记录历史最多缓存个数及时间点*/
private long highestSize = 0;
private Date highestTime;
private volatile LoadingCache<K, V> cache;
复制代码
这里先是定义了一些常量和基本的属性信息,固然这些属性会提供set&get方法,供实际使用时去自行设置。
public LoadingCache<K, V> getCache() {
//使用双重校验锁保证只有一个cache实例
if(cache == null){
synchronized (this) {
if(cache == null){
//CacheBuilder的构造函数是私有的,只能经过其静态方法newBuilder()来得到CacheBuilder的实例
cache = CacheBuilder.newBuilder()
//设置缓存容器的初始容量为100
.initialCapacity(DEFAULT_SIZE)
//缓存数据的最大条目
.maximumSize(maxSize)
//定时回收:缓存项在给定时间内没有被写访问(建立或覆盖),则回收。
.expireAfterWrite(expireTime, timeUnit)
//启用统计->统计缓存的命中率等
.recordStats()
//设置缓存的移除通知
.removalListener((notification)-> {
if (LOGGER.isDebugEnabled()){
LOGGER.debug("{} was removed, cause is {}" ,notification.getKey(), notification.getCause());
}
})
.build(new CacheLoader<K, V>() {
@Override
public V load(K key) throws Exception {
return fetchData(key);
}
});
this.resetTime = new Date();
this.highestTime = new Date();
if (LOGGER.isInfoEnabled()){
LOGGER.info("本地缓存{}初始化成功.", this.getClass().getSimpleName());
}
}
}
}
return cache;
}
复制代码
上面这段代码是整个缓存的核心,经过这段代码来生成咱们的缓存对象【使用了单例模式】。具体的属性参数看注释。
由于上面的那些都是封装在一个抽象类AbstractGuavaCache里面的,因此我又封装了一个CacheManger用来管理缓存,并对外提供具体的功能接口;在CacheManger中,我使用了一个静态内部类来建立当前默认的缓存。
/** * 使用静态内部类实现一个默认的缓存,委托给manager来管理 * * DefaultGuavaCache 使用一个简单的单例模式 * @param <String> * @param <Object> */
private static class DefaultGuavaCache<String, Object> extends AbstractGuavaCache<String, Object> {
private static AbstractGuavaCache cache = new DefaultGuavaCache();
/** * 处理自动载入缓存,按实际状况载入 * 这里 * @param key * @return */
@Override
protected Object fetchData(String key) {
return null;
}
public static AbstractGuavaCache getInstance() {
return DefaultGuavaCache.cache;
}
}
复制代码
大概思路就是这样,若是须要扩展,咱们只须要按照实际的需求去扩展AbstractGuavaCache这个抽象类就能够了。具体的代码贴在下面了。
public abstract class AbstractGuavaCache<K, V> {
protected final Logger LOGGER = LoggerFactory.getLogger(AbstractGuavaCache.class);
private static final int MAX_SIZE = 1000;
private static final int EXPIRE_TIME = 10;
/** 用于初始化cache的参数及其缺省值 */
private static final int DEFAULT_SIZE = 100;
private int maxSize = MAX_SIZE;
private int expireTime = EXPIRE_TIME;
/** 时间单位(分钟) */
private TimeUnit timeUnit = TimeUnit.MINUTES;
/** Cache初始化或被重置的时间 */
private Date resetTime;
/** 分别记录历史最多缓存个数及时间点*/
private long highestSize = 0;
private Date highestTime;
private volatile LoadingCache<K, V> cache;
public LoadingCache<K, V> getCache() {
//使用双重校验锁保证只有一个cache实例
if(cache == null){
synchronized (this) {
if(cache == null){
//CacheBuilder的构造函数是私有的,只能经过其静态方法ne
//wBuilder()来得到CacheBuilder的实例
cache = CacheBuilder.newBuilder()
//设置缓存容器的初始容量为100
.initialCapacity(DEFAULT_SIZE)
//缓存数据的最大条目
.maximumSize(maxSize)
//定时回收:缓存项在给定时间内没有被写访问
//(建立或覆盖),则回收。
.expireAfterWrite(expireTime, timeUnit)
//启用统计->统计缓存的命中率等
.recordStats()
//设置缓存的移除通知
.removalListener((notification)-> {
if (LOGGER.isDebugEnabled()){
//...
}
})
.build(new CacheLoader<K, V>() {
@Override
public V load(K key) throws Exception {
return fetchData(key);
}
});
this.resetTime = new Date();
this.highestTime = new Date();
if (LOGGER.isInfoEnabled()){
//...
}
}
}
}
return cache;
}
/** * 根据key从数据库或其余数据源中获取一个value,并被自动保存到缓存中。 * * 改方法是模板方法,子类须要实现 * * @param key * @return value,连同key一块儿被加载到缓存中的。 */
protected abstract V fetchData(K key);
/** * 从缓存中获取数据(第一次自动调用fetchData从外部获取数据),并处理异常 * @param key * @return Value * @throws ExecutionException */
protected V getValue(K key) throws ExecutionException {
V result = getCache().get(key);
if (getCache().size() > highestSize) {
highestSize = getCache().size();
highestTime = new Date();
}
return result;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public int getExpireTime() {
return expireTime;
}
public void setExpireTime(int expireTime) {
this.expireTime = expireTime;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
public void setTimeUnit(TimeUnit timeUnit) {
this.timeUnit = timeUnit;
}
public Date getResetTime() {
return resetTime;
}
public void setResetTime(Date resetTime) {
this.resetTime = resetTime;
}
public long getHighestSize() {
return highestSize;
}
public void setHighestSize(long highestSize) {
this.highestSize = highestSize;
}
public Date getHighestTime() {
return highestTime;
}
public void setHighestTime(Date highestTime) {
this.highestTime = highestTime;
}
}
复制代码
public class DefaultGuavaCacheManager {
private static final Logger LOGGER =
LoggerFactory.getLogger(DefaultGuavaCacheManager.class);
//缓存包装类
private static AbstractGuavaCache<String, Object> cacheWrapper;
/** * 初始化缓存容器 */
public static boolean initGuavaCache() {
try {
cacheWrapper = DefaultGuavaCache.getInstance();
if (cacheWrapper != null) {
return true;
}
} catch (Exception e) {
LOGGER.error("Failed to init Guava cache;", e);
}
return false;
}
public static void put(String key, Object value) {
cacheWrapper.getCache().put(key, value);
}
/** * 指定缓存时效 * @param key */
public static void invalidate(String key) {
cacheWrapper.getCache().invalidate(key);
}
/** * 批量清除 * @param keys */
public static void invalidateAll(Iterable<?> keys) {
cacheWrapper.getCache().invalidateAll(keys);
}
/** * 清除全部缓存项 : 慎用 */
public static void invalidateAll() {
cacheWrapper.getCache().invalidateAll();
}
public static Object get(String key) {
try {
return cacheWrapper.getCache().get(key);
} catch (Exception e) {
LOGGER.error("Failed to get value from guava cache;", e);
}
return null;
}
/** * 使用静态内部类实现一个默认的缓存,委托给manager来管理 * * DefaultGuavaCache 使用一个简单的单例模式 * @param <String> * @param <Object> */
private static class DefaultGuavaCache<String, Object> extends AbstractGuavaCache<String, Object> {
private static AbstractGuavaCache cache = new DefaultGuavaCache();
/** * 处理自动载入缓存,按实际状况载入 * @param key * @return */
@Override
protected Object fetchData(String key) {
return null;
}
public static AbstractGuavaCache getInstance() {
return DefaultGuavaCache.cache;
}
}
}
复制代码