如今, 分布式缓存大行其道,比如Redis等, 但有些时候, 我们仅仅需要一个轻量级的本地缓存.
本地缓存的使用场景: 不需要被分布式共享、不需要持久化、数据规模小、更新频率低
本地缓存的使用优势:运行速度更快(因为无需进程或网络通信), 节省开发资源
本地缓存的设计重点:
1, 过期删除策略
2, 缓存淘汰策略
我们比较熟知的Java本地缓存有谷歌的Guava, 但Spring5已经弃用Guava而选择了Caffeine, 据说后者性能更佳, “缓存淘汰策略”设计的更加优秀.
下面说一下上面提到的2种策略
过期删除策略
对于设置了过期时间的缓存,删除策略一般分为: 定期删除、惰性删除.
定期删除:单独的线程对数据进行过期检查
惰性删除:当数据被查询时再判断是否过期
缓存淘汰策略
当缓存容量不足时, 需要开启淘汰机制, 缓存淘汰策略常见的有: FIFO、LRU、LFU
FIFO(First In First out): 先进先出
当资源不足时, 干掉那些先出生的老人, 简单粗暴, 但是可能造成经常使用的缓存被无辜淘汰.
LRU(Least recently used): 最近最少使用
LRU机制要求put缓存时,将数据放在链表的头部, get缓存时将数据移到链表的头部, 保证了最近使用的数据都在链表的头部区域, 最近最少使用的数据都在链表的尾部, 这样需要淘汰缓存时, 直接移除链表尾部的数据即可.
LFU(Least frequently used): 最少频率使用
LFU和LRU虽然都是基于“最少”使用,但“率”似乎更加准确, 就像现实中,我们经常用“增长率”来衡量增长指标一样.
上面3种策略, 最常使用的是LRU, 可以满足绝大多数场景, 实现起来也比较简单, Guava就采用了LRU策略, 但总有人精益求精, 提出一些新的策略, 比如Caffeine的W-TinyLFU.
如果一个独立的应用, 可预见的数据规模小,不需要考虑内存不足时“缓存淘汰”,只需要“缓存过期”机制, 则我们可以自定义一个简单的本地缓存.
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
/**
* 简单本地缓存器,适用于小规模的静态数据缓存。
* 失效策略:"惰性删除",即每次查询时进行失效判断
* 淘汰策略: 无
* @Author:tt
* @Description:
* @CreateTime:2018/11/11
*/
public class SimpleLocalCache {
//ConcurrentHashMap并发读写
private static ConcurrentHashMap cacheArea = new ConcurrentHashMap<>();
//取缓存
public static Object get(String key) {
CacheData cacheData = cacheArea.get(key);
if (cacheData == null) {
return null;
}
if (isDead(cacheData)) {
//惰性删除
remove(key);
return null;
}
return cacheData.getValue();
}
//放缓存
public static boolean put(String key, Object value, long maxAge) {
CacheData data = new CacheData();
data.setCreateTime(new Date().getTime());
data.setMaxAge(maxAge);
data.setKey(key);
data.setValue(value);
cacheArea.put(key, data);
return false;
}
//是否过期
private static boolean isDead(CacheData data) {
if (data == null) {
return true;
}
if (data.getMaxAge() == 0) {
return false;
}
long createTime = data.getCreateTime();
long deadTime = createTime + data.getMaxAge();
long nowTime = new Date().getTime();
if (nowTime > deadTime) {
return true;
}
return false;
}
//移除
public static boolean remove(String key) {
cacheArea.remove(key);
return true;
}
//缓存数据封装
static class CacheData {
//创建时间:单位毫秒
private long createTime;
//缓存时常:单位毫秒, 0-标示永不失效
private long maxAge;
//数据key
private String key;
//数据value
private Object value;
public long getCreateTime() {
return createTime;
}
public void setCreateTime(long createTime) {
this.createTime = createTime;
}
public long getMaxAge() {
return maxAge;
}
public void setMaxAge(long maxAge) {
this.maxAge = maxAge;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
//测试
public static void main(String[] args) throws InterruptedException {
SimpleLocalCache.put("key1", "v1", 1000);
SimpleLocalCache.put("key2", "v2", 5000);
SimpleLocalCache.put("key3", "v3", 0);
Thread.sleep(3000);
System.out.println(SimpleLocalCache.get("key1"));
System.out.println(SimpleLocalCache.get("key2"));
System.out.println(SimpleLocalCache.get("key3"));
}
}