几种方案:
1、分布式缓存中间件
2、前端使用 localstorage 设置浏览器缓存
3、本地缓存
优缺点
1、引入分布式缓存储存后端数据
• 优点
效率高,资源占用少,管理统一
• 难点(※※)
接入成本(需调研)
2、前端使用 浏览器缓存(localstorage)保存各种数据
• 优点
2.1、极大提高网页渲染速度,增强体验
2.2、降低后端请求数量,减小服务器压力和DB压力,增强程序运行效率
• 难点(※)
接入成本(已做过调研,还需确定完整方案)
3、后端本地缓存
• 优点
3.1、提高程序运行效率,减少DB压力
3.2、接入成本小
• 不足
占用系统内存,可能影响性能,也可能带来内存占用问题(需验证评估)
使用单例模式实现本地缓存
代码
本地缓存工具类
import jodd.madvoc.meta.In;
import java.io.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* @ClassName: LocalCache
* @Description: 本地缓存
**/
public class LocalCache {
// 启动监控线程
static {
new Thread(new TimeoutTimerThread()).start();
}
/**
* logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(LocalCache.class);
/**
* 默认的缓存容量
*/
private static int DEFAULT_CAPACITY = 512;
/**
* 最大容量
*/
private static int MAX_CAPACITY = 100000;
/**
* 缓存永不过期
*/
public static final Integer UN_EXPIRED_TIME = -1;
/**
* 保证线程安全,使用concurrentHashMap
*/
private static final ConcurrentHashMap<String , CacheEntity> cache = new ConcurrentHashMap<String , CacheEntity>();
/**
* 定时清空缓存(分钟),默认10分钟
*/
private static volatile int clearAllInterval = 1;
private static LocalCache instance;
// 构造方法私有化,防止实例化
private LocalCache() {}
// 单例模式构建对象
public static LocalCache getInstance(){
if (instance == null) {
instance = new LocalCache();
}
return instance;
}
/**
* 1、将key-value 保存到本地缓存并制定该缓存的过期时间
*
* @param key
* @param value
* @param expireTime 过期时间,如果是-1 则表示永不过期
* @return boolean
*/
public boolean putValue(String key, Object value, int expireTime) {
if (key == null || "".equals(key) || value == null) {
LoggerUtil.warn(LOGGER, "[LocalCache-putValue]-blank-key-or-empty-value: " + key);
}
LoggerUtil.info(LOGGER, "[LocalCache-putValue]-key: " + key);
return putCloneValue(key, value, expireTime);
}
/**
* 2、从本地缓存中获取key对应的值,如果该值不存则则返回null
* @param key
* @param interval 定时清理缓存的时长
* @return Object
*/
public Object getValue(String key, Integer interval) {
if (interval != null) {
clearAllInterval = interval;
}
boolean unCachedFlag = key == null || "".equals(key) || cache.get(key) == null;
if (unCachedFlag) {
LoggerUtil.warn(LOGGER, "[LocalCache-getValue]-warn-blank-key-or-empty-value");
return null;
}
LoggerUtil.info(LOGGER, "[LocalCache-getValue]-getValueFromCache-Key: " + key);
return cache.get(key).getValue();
}
/**
* 3、从本地缓存中获取key对应的值,如果该值不存则则返回null
* @param key
* @param interval 定时清理缓存的时长
* @param doCacheFlag 缓存开关
* @return Object
*/
public Object getValue(String key, Integer interval, Boolean doCacheFlag) {
if (interval != null) {
clearAllInterval = interval;
}
boolean unCachedFlag = key == null || "".equals(key) || cache.get(key) == null || (doCacheFlag != null && !doCacheFlag);
if (unCachedFlag) {
LoggerUtil.warn(LOGGER, "[LocalCache-getValue]-warn-blank-key-or-empty-value");
return null;
}
LoggerUtil.info(LOGGER, "[LocalCache-getValue]-getValueFromCache-Key: " + key);
return cache.get(key).getValue();
}
/**
* 4、清除指定缓存
*/
public void clearByKey(String key) {
if (key == null || "".equals(key)) {
LoggerUtil.warn(LOGGER, "[LocalCache-clearByKey]-warn-blank-key");
return;
}
cache.remove(key);
}
/**
* 5、清空所有
*/
public void clearAll() {
cache.clear();
}
/**
* 将值通过序列化clone 处理后保存到缓存中,可以解决值引用的问题
*
* @param key
* @param value
* @param expireTime 缓存过期时间
* @return boolean
*/
private boolean putCloneValue(String key, Object value, int expireTime) {
try {
if (cache.size() >= MAX_CAPACITY) {
LoggerUtil.warn(LOGGER, "[LocalCache-putCloneValue]-warn-缓存容量达到阈值,不再新增缓存");
return false;
}
// 序列化赋值
CacheEntity entityClone = clone(new CacheEntity(value, System.nanoTime(), expireTime));
cache.put(key, entityClone);
return true;
} catch (Exception e) {
LoggerUtil.error(LOGGER, e, "[LocalCache-putCloneValue]-error " + StackPrint.getTrace(e));
}
return false;
}
/**
* 序列化 克隆处理
*
* @param object
* @return
*/
private <T extends Serializable> T clone(T object) {
T cloneObject = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
cloneObject = (T) ois.readObject();
ois.close();
} catch (Exception e) {
LoggerUtil.error(LOGGER, e, "[LocalCache-clone]-error " + StackPrint.getTrace(e));
}
return cloneObject;
}
/**
* 过期处理线程
*
*/
private static class TimeoutTimerThread implements Runnable {
public void run() {
while (true) {
try {
LoggerUtil.info(LOGGER, "[LocalCache-TimeoutTimerThread]-start-Cache-monitor");
TimeUnit.MINUTES.sleep(clearAllInterval);
// checkTime();
instance.clearAll();
} catch (Exception e) {
LoggerUtil.error(LOGGER, e, "[LocalCache-TimeoutTimerThread]-error " + StackPrint.getTrace(e));
}
}
}
/**
* 过期缓存的具体处理方法
*
*/
private void checkTime() {
// 开始处理过期缓存
for (String key : cache.keySet()) {
CacheEntity tce = cache.get(key);
long gmtModify = tce.getGmtModify();
// 分钟转换为纳秒
gmtModify = gmtModify * 60000000000L;
long timoutTime = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - gmtModify);
// 过期时间 > timoutTime;
if (tce.getExpire() > timoutTime) {
continue;
}
LoggerUtil.info(LOGGER, "[LocalCache-checkTime]-清除过期缓存: " + key);
// 清除过期缓存和删除对应的缓存队列
cache.remove(key);
}
}
}
}
模型对象
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* @ClassName: CacheEntity
* @Description: 保存本地缓存的实体
**/
@Setter
@Getter
public class CacheEntity implements Serializable {
/** */
private static final long serialVersionUID = 7172649826282703560L;
/**
* 值
*/
private Object value;
/**
* 保存的时间戳
*/
private long gmtModify;
/**
* 过期时间(分钟)
*/
private int expire;
public CacheEntity(Object value, long gmtModify, int expire) {
super();
this.value = value;
this.gmtModify = gmtModify;
this.expire = expire;
}
}
调用方式
// 清除缓存
LocalCache.getInstance().clearByKey(bdDictionary.getEname());
// 获取实例
LocalCache cacheUtil = LocalCache.getInstance();
// 通过key获取对应缓存
cacheUtil.getValue(key, getCacheClearInterval(), doCacheFlag)
问题(todo)
代码中还有个PMD问题
Non-thread safe singletons can result in bad state changes. Eliminate static singletons if possible by instantiating the object directly. Static singletons are usually not needed as only a single instance exists anyway. Other possible fixes are to synchronize the entire method or to use an initialize-on-demand holder class. Refrain from using the double-checked locking pattern. The Java Memory Model doesn't guarantee it to work unless the variable is declared as
volatile
, adding an uneeded performance penalty. Reference See Effective Java, item 48.
Example:
private static Foo foo = null;
//multiple simultaneous callers may see partially initialized objects
public static Foo getFoo() {
if (foo==null) {
foo = new Foo();
}
return foo;
}