1 什么是本地缓存;
本地:是根据业务系统角度来说的,是指该业务系统部署所在的服务器;
缓存:缓存是指用于通过将经常访问的数据临时存储某个位置,用来提高应用程序的性能和效率,是一种空间换时间的手段。
总结来说,本地缓存是将需要频繁访问的数据临时存储在服务部署的服务器上。
注:缓存分类:可以分为内存缓存,硬盘缓存,分布式缓存,项目中常说的本地缓存是内存缓存。
2 本地缓存使用场景;
读取的数据需要满足以下特点
2.1 对数据实时性要求不高;就是数据变动频率比较低,更多是读取操作;
2.2 分布式部署时多台机器数据不一致问题;
2.3 对吞吐量要求比较高,分布式缓存(如redis做一级缓存)性能无法满足时作为二级缓存;
3 有哪些本地缓存方式;
1 集合类list,hashMap等,多线程下使用并发安全类CurrentHashMap;
2 常见的GuavaCache和Caffeine;
如何选择?
如果缓存使用方式不需要淘汰过期等策略,可以自己封装缓存类;
使用三方的缓存功能更多,如支持最大容量限制,过期删除策略等;
4 Caffeine是什么?
官方简介:Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库。
缓存和ConcurrentMap有点相似,但还是有所区别。最根本的区别是ConcurrentMap将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。但是,Caffeine的缓存Cache 通常会被配置成自动驱逐缓存中元素,以限制其内存占用。在某些场景下,LoadingCache和AsyncLoadingCache 因为其自动加载缓存的能力将会变得非常实用。
功能:
中文文档git地址:https://github.com/ben-manes/caffeine/wiki/Home-zh-CN; 详情了解可以查看官方文档,下面的就可以不用看了。
提前了解部分知识点:
4.1常见淘汰算法:
1 先进先出 (First In First Out),简称FIFO;
2 最近最久未使用 (Least Recently Used),简称LRU,基于访问时间来实现;
3 最近最小频率使用 (Least Frequently Used),简称LFU,LFU基于访问频率实现;
4.2 W-TinyLFU是什么?
Window TinyLFU是一种高效的缓存设计方案,它结合了LFU(Least Frequently Used)和LRU(Least Recently Used)算法的优点,旨在提供高命令率和低内存占用。具体的实现
4.3 Caffeine提供了四种缓存添加策略:
● 手动加载cache
java
Cache<Object, Object> cache = Caffeine.newBuilder()
//初始数量
.initialCapacity(10)
//最大条数
.maximumSize(10)
//expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准
//最后一次写操作后经过指定时间过期
.expireAfterWrite(1, TimeUnit.SECONDS)
//最后一次读或写操作后经过指定时间过期
.expireAfterAccess(1, TimeUnit.SECONDS)
//监听缓存被移除
.removalListener((key, val, removalCause) -> { })
//记录命中
.recordStats()
.build();
cache.put('key','value');//相同的key会覆盖
cache.get('key', k -> value);//该函数get为原子操作,另一个线程同时调用本方法进行竞争,则后一线程会被阻塞,直到前一线程更新缓存完成;
cache.getIfPresent('1');//会立即返回null,不会被阻塞
// 移除一个缓存元素
cache.invalidate(key);
● 自动加载LoadingCache
java
LoadingCache<String, String> cache = Caffeine.newBuilder()
//创建缓存或者最近一次更新缓存后经过指定时间间隔,刷新缓存;refreshAfterWrite仅支持LoadingCache
.refreshAfterWrite(10, TimeUnit.SECONDS)
.expireAfterWrite(10, TimeUnit.SECONDS)
.expireAfterAccess(10, TimeUnit.SECONDS)
.maximumSize(10)
.build(key -> createExpensiveGraph(key));
private String createExpensiveGraph(String k) {
return k + "+default";
}
// 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null
Graph graph = cache.get(key);
// 批量查找缓存,如果缓存不存在则生成缓存元素
Map<Key, Graph> graphs = cache.getAll(keys);
● 手动异步加载AsyncCache
java
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
//创建缓存或者最近一次更新缓存后经过指定时间间隔刷新缓存;仅支持LoadingCache
.refreshAfterWrite(1, TimeUnit.SECONDS)
.expireAfterWrite(1, TimeUnit.SECONDS)
.expireAfterAccess(1, TimeUnit.SECONDS)
.maximumSize(10)
//根据key查询数据库里面的值
.buildAsync();
// 查找一个缓存元素, 没有查找到的时候返回null
CompletableFuture<Graph> graph = cache.getIfPresent(key);
// 查找缓存元素,如果不存在,则异步生成
graph = cache.get(key, k -> createExpensiveGraph(key));
// 添加或者更新一个缓存元素
cache.put(key, graph);
// 移除一个缓存元素
cache.synchronous().invalidate(key);
● 自动异步加载AsyncLoadingCache
java
AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
// 你可以选择: 去异步的封装一段同步操作来生成缓存元素
.buildAsync(key -> createExpensiveGraph(key));
// 你也可以选择: 构建一个异步缓存元素操作并返回一个future
.buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
// 查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Graph> graph = cache.get(key);
// 批量查找缓存元素,如果其不存在,将会异步进行生成
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);
4.4 驱逐:Caffeine 提供了三种驱逐策略,分别是基于容量,基于时间和基于引用三种类型。
基于容量:
java
// 基于缓存内的元素个数进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10_000)
.build(key -> createExpensiveGraph(key));
// 基于缓存内元素权重进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumWeight(10_000)
.weigher((Key key, Graph graph) -> graph.vertices().size())
.build(key -> createExpensiveGraph(key));
基于时间:
java
// 基于固定的过期时间驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
// 基于不同的过期驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.expireAfter(new Expiry<Key, Graph>() {
public long expireAfterCreate(Key key, Graph graph, long currentTime) {
// Use wall clock time, rather than nanotime, if from an external resource
long seconds = graph.creationDate().plusHours(5)
.minus(System.currentTimeMillis(), MILLIS)
.toEpochSecond();
return TimeUnit.SECONDS.toNanos(seconds);
}
public long expireAfterUpdate(Key key, Graph graph,
long currentTime, long currentDuration) {
return currentDuration;
}
public long expireAfterRead(Key key, Graph graph,
long currentTime, long currentDuration) {
return currentDuration;
}
})
.build(key -> createExpensiveGraph(key));
Caffeine提供了三种方法进行基于时间的驱逐:
expireAfterAccess(long, TimeUnit): 一个元素在上一次读写操作后一段时间之后,在指定的时间后没有被再次访问将会被认定为过期项。
expireAfterWrite(long, TimeUnit): 一个元素将会在其创建或者最近一次被更新之后的一段时间后被认定为过期项。在对被缓存的元素的时效性存在要求的场景下,这是理想的选择。
expireAfter(Expiry): 一个元素将会在指定的时间后被认定为过期项。当被缓存的元素过期时间收到外部资源影响的时候,这是理想的选择。
为了使过期更有效率,可以通过在你的Cache构造器中通过Scheduler接口和Caffeine.scheduler(Scheduler) 方法去指定一个调度线程对过期事件进行调度。
基于引用
java
// 当key和缓存元素都不再存在其他强引用的时候驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.weakKeys()
.weakValues()
.build(key -> createExpensiveGraph(key));
// 当进行GC的时候进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.softValues()
.build(key -> createExpensiveGraph(key));
4.5 移除:显式移除和监听器移除;
说明:
驱逐 缓存元素因为策略被移除
失效 缓存元素被手动移除
移除 由于驱逐或者失效而最终导致的结果