前言
为什么会用到Guava catch 呢,我是在做一个接口鉴权的时候发现这个鉴权过程需要请求4次Redis信息,而这个接口每秒钟需要请求上百次,这样去Redis里面拿信息与Redis交互的过程严重影响了调用接口的速率,要解决这个问题所以就想到了用Guava catch 将第一次从Redis中请求的数据缓存到内存中一分钟,然后接下来一分钟去JVM内存中去拿数据,一分钟后再请求一下Redis数据继续缓存到Guava catch 容器中(保证了数据的可靠性),就这样速度提升了70%以上,符合了要求~~当然 也是考虑到鉴权的信息数据量没有太大,才敢使用的Guava catch,数据量大的话 不推荐使用。
一、Guava catch是什么?
Guava catch 就是一个缓存容器,将一些数据缓存到JVM内存中,这样就意味着缓存的信息只能当前实例使用,不能跨服务,如果这个项目有多个实例,会造成数据的冗余。适合缓存数据量小,使用频率高、修改不频繁的数据。
二、使用步骤
1.引入依赖
代码如下(示例):
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
2.构建工具类
代码如下(示例):这是一个抽象类下面有他的实现工具类(这两个工具类也是在网上找的,很好用 复制粘贴 拿来即用)
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 = 60;
/** 用于初始化cache的参数及其缺省值 */
private static final int DEFAULT_SIZE = 100;
private int maxSize = MAX_SIZE;
private int expireTime = EXPIRE_TIME;
/** 时间单位(分钟) */
private TimeUnit timeUnit = TimeUnit.SECONDS;
/** 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)
//定时回收:缓存项在一段时间内会失效需要重新添加
.expireAfterAccess(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);
LOGGER.info("----从缓存中获取信息失败----");
return null;
}
}
public static Object getAll(List<String> clientIds){
try {
return cacheWrapper.getCache().getAll(clientIds);
} 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;
}
}
}
请注意你在设置了Guava catch 的回收策略后(expireAfterWrite或者expireAfterAccess),结果就是 :如果这个键失效了 你去获取这个键的值的时候会抛异常的,我在上面的工具类 我把抛异常信息的代码注释掉了。。抛异常也不会影响程序的运行的,是Guava catch 内部的问题,不用担心(这个是我的测试结果。。。)。如果你缓存的是不需要修改或更新的那就不用设置回收策略了,那样就不会报错了。
下满是两种过期策略我自己的理解:
expireAfterWrite:指的是如果这个键值没有读写取操作(没有被用到),他会根据最后一次操作时间开始计算直至达到你设置的过期时间然后失效(一直被用一直不会失效)。
expireAfterAccess:指的是严格按照过期时间过期(无论用不用都会过期)。我用的是这种,过期了会去Redis取。
3.使用
使用也很方便 直接调用上面工具类的get(),put()方法就行了,就是需要先启动容器。我是这样做的(如果你有更好的做法可以不必按照我的写法);
这段代码需要写到被spring能加载到容器内的类中(就是加了spring注解的类中比如(@Controller,@Component))。
@PostConstruct
public void startGuava() {
DefaultGuavaCacheManager.initGuavaCache();
}
下面是调用的方式。
DefaultGuavaCacheManager.get("");
DefaultGuavaCacheManager.put("",Object);
到这我的需求就解决了。
总结
我也是边学边用,有哪里写的不对的地方,希望评论给我指出来,一起进步。用完Guava catch 速度确实提高了很多!!!