问题场景:
缓存资源(如:Redis)稀缺,但是又希望用到缓存的情况。比如:通过Rpc接口获取的城市的信息(城市ID、名等,数据变动频率很低)、数据库配置的数据(如果用Apollo、Nacos配置中心的就没必要了)、数据库计算的topN数据(业务需要,但是,不能频繁计算)。
实现思路:
1、缓存最大个数(cachMaxSize):机器的内存资源也是有限的,所以,需要限制ConcurrentHashMap占用的内存空间。
private static final Integer CACHE_MAX_SIZE = 100000;
2、缓存(cacheMap):
private static final Map cacheMap = new ConcurrentHashMap<>();
注:MapCacheObj结构为:
//缓存对象
private Object CacheValue;
//缓存过期时间
private long ttlTime;
}
3、缓存使用记录(cacheLogList):
如果key如果被获取过,那么,记录一次使用记录,节点插入到链表到最前面(注:插入前需要遍历删除已经存在的同名到key,这样在做淘汰策略时,只需要删除尾部的节点对应的key即可)(注:此处数据结构如果该用数组将更高效)。
private static final List CACHE_LOG_LIST = new LinkedList<>();
4、守护线程(辅助删除过期key,在获取key的时候还会做一次是否过期的校验):
开启一个线程,定时扫描key超时信息,并删除过期的key。
5、Key失效:
每次获取key的时候,都需要做一次时间是否过期判断,如果过期了,那么就返回空,同时删除掉key(注:守护线程也起到一定到辅助作用)。
6、最近未使用(lastest unused):
根据缓存使用记录cacheLogList来判断,链表末尾的就是最久未使用的,优先淘汰删除。
7、Key、Value的序列化(fastjson):
例如:存储的时用户信息的List,那么,将list转为JSONString做为value存储。
获取的时候,解析方式:JSON.parseArray((String)object, UserDto.class);(注意:小技巧:key的命名单独定义在一个类中几种管理,如果时List那么在命名的尾部做标示,这样解析的时候就明确用JSON.parseArray还是JSON.parse)
//将用户信息list序列化为json string
List userDtos = ...;
String value = JSON.toJSONString(userDtos);
......
......
//反序列化
List userDtoList = JSON.parseArray((String)value, RingTypeTreeDto.class);
代码实现:
https://github.com/sijunx/mySpider/blob/feature_word_dic_20191001001/spider-base/spider-base-utils/src/main/java/com/spider/base/utils/MapCacheUtils.java