LinkedHashMapCache设计问题讨论
LinkedHashMapCache使用LinkedHashMap实现,实例化时会固定传入accessOrder=true,当accessOrder为true时访问数据后会将当前节点移到队列尾部,当过期策略设置为isExpireAfterAccess时没有问题,最近访问的数据会放到队尾那么队列头部均是最早被淘汰的,但是当过期策略是isExpireAfterWrite时就会因这些移尾的行为打乱进入队列的位置,队列头部的数据并不能保证优先被淘汰,只有保留入队顺序才能更快的找到淘汰数据,所以需要根据config里的过期策略来设计accessOrder的值。
优化建议:
- LRUMap实例化时根据过期策略判断accessOrder值
- 过期扫描不需要扫描全表只需要扫描队列头即可,当出现第一个不满足淘汰的节点即中断
- 可以设置单次淘汰数量以缓解因数量过多持有锁时间长影响正常访问
另外建议LinkedHashMapAutoConfiguration添加map关键词,linkedhashmap名称太长且不需要向外暴露实现方法,改为map更简洁且不容易拼写错误
jetcache:
local:
default:
type: map
keyConvertor: fastjson
因为内存有限,基于内存的cache一般要基于LRU算法做淘汰,这就是accessOrder固定为true的原因。
expireAfterWriteInMillis和expireAfterAccessInMillis是可以同时存在的。
expireAfterWriteInMillis存在的意义是让缓存定期失效,在很多不那么严格的场景下,通过它就实现了缓存数据的定期刷新。
expireAfterAccessInMillis存在的意义是,清除掉Cache中一些命中率低的item,以节约内存。实际上在jetcache中它没有头很好的被实现。比如localLimit为100,但实际上只有10个元素经常命中,另外90个如果expireAfterWriteInMillis较长,但一直没有被访问,应该通过expireAfterAccessInMillis来干掉。对LinkedHashMapCache来说应该有个扫描器在后台去干这事。不过这很少有人问,也就没实现,毕竟自己手搓的比较简陋。CaffeineCache是有的。
同意你的观点,但对容量较小的缓存来说不主动清理会更好,总体容积较小时清理缓存并不会对内存有多少影响,只需要插入新数据时如果满缓存则清除最早入队或者最早被访问的节点即可。
我们的实际使用场景只有当limit大于256时才会将它放入Clean队列里,当然这个还是要看使用场景并不能说这个方式更优,相比起来直接使用CaffeineCache更好。
protected void addToCleaner() {
// When the cache size is small, it can be stored in memory for a long time
if (config.getLimit() > 256) {
Cleaner.add(this);
}
}