文章目录
使用场景
随着互联网用户越来越多,并发量、吞吐量越来越大
本地缓存的应用场景:
- 对性能有非常高的要求
- 不经常变化
- 占用内存不大
- 有访问整个集合的需求
- 数据允许不时时一致
例如:拉勾网首页,由于首页经常被访问,可以将职位信息:java开发、大数据开发等放在本地缓存中。
guava cache:高并发,不需要持久化
currentHashMap:高并发
Ehcached:持久化 二级缓存
Guava Cache 的优势
缓存机制淘汰算法可参考博文:本地缓存之LRU FIFO实现
- 缓存过期和淘汰机制:LRU
- 并发处理能力:类似CurrentHashMap,是线程安全的,采用了分段锁机制,将一个集合分成若干个人partiton ,每个Patrtiton一把锁,master多分区,利用segement作分区
- 更新锁定:GuavaCache可以在CacheLoader的load方法中加以控制,对同一个key,只让一个请求去读源并回填缓存,其他请求阻塞等待。.
- 集成数据源:而GuavaCache的get可以集成数据源,在从缓存中读取不到时可以从数据源中读取数据并回填缓
存 - 监控缓存加载/命中情况
Guava Cache使用
Cache创建:
方法 | 作用 |
---|---|
maximumSize | 容量 |
expireAfterWrite | 缓存项在给定时间内没有被写访问(创建或覆盖),则回收 |
recordStats | 缓存项在给定时间内没有被读/写访问,则回收 |
removalListener | 移除监听器 |
weakKeys | 弱引用存储键,当键没有其它(强或软)引用时,缓存项可以被垃圾回收 |
weakValues | 使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收 |
concurrencyLevel | 并发操作 |
CacheLoader、Callable功能相同,都是在调用get方法时候,如果缓存不存在则指定数据源加载
CacheLoader
在创建缓存对象初始化时使用
模拟数据源
public static HashMap<Integer, Integer> sourceMap = new HashMap<>();
static {
for (int i = 0; i < 10; i++){
sourceMap.put(i, i);
}
}
使用demo
public static void main(String[] args) throws ExecutionException {
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(5).build(new CacheLoader<Object, Object>() {
@Override
public Object load(Object o) throws Exception {
return sourceMap.get(o);
}
});
for (int i = 0; i < 10; i++){
cache.get(i);
}
System.out.println(cache.size());
System.out.println(cache.asMap());
}
Callable
调用get方法时当缓存数据不存在时候从数据源加载数据
使用demo:
@Test
public void call() throws ExecutionException {
Cache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(5).build();
Object value = cache.get(1, new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return sourceMap.get(1);
}
});
System.out.println(value);
}
删除
主动删除
@Test
public void doDel() throws ExecutionException {
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(5).expireAfterAccess(3, TimeUnit.SECONDS).build(new CacheLoader<Object, Object>() {
@Override
public Object load(Object o) throws Exception {
return sourceMap.get(o);
}
});
initCache(cache);
//主动删除 key为1
cache.invalidate(1);
System.out.println("主动删除");
displayCache(cache);
// 批量删除
cache.invalidateAll(Arrays.asList(1,2));
System.out.println("批量删除");
displayCache(cache);
}
过期删除
expireAfterAccess:如果在一定时间内没被访问则数据过期
@Test
public void expireTimeDel() throws ExecutionException, InterruptedException {
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(5).expireAfterAccess(3, TimeUnit.SECONDS).build(new CacheLoader<Object, Object>() {
@Override
public Object load(Object o) throws Exception {
return sourceMap.get(o);
}
});
initCache(cache);
Thread.sleep(1000);
cache.getIfPresent(1);
Thread.sleep(2000);
displayCache(cache);
}
基于容量删除
@Test
public void sizeDel() throws ExecutionException {
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(1).build(new CacheLoader<Object, Object>() {
@Override
public Object load(Object o) throws Exception {
return sourceMap.get(o);
}
});
Object v = cache.get(1);
System.out.println(v);
//自动删除1
Object v2 = cache.get(2);
displayCache(cache);
}
引用删除
开启weakValues功能,采用弱引用,对引用不了解的可以看看,我的文章
@Test
public void referenceDel(){
Cache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(3).weakValues().build();
cache.put("1",new Object());
//强制垃圾回收
System.gc();
System.out.println(cache.getIfPresent("1"));
}
高级用法
并发设置
设置 concurrencyLevel 使得缓存支持并发的写入和读取
Cache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(3).concurrencyLevel(Runtime.getRuntime().availableProcessors()).build();
更新锁定
GuavaCache提供了一个refreshAfterWrite定时刷新数据的配置项,如果经过一定时间没有更新或覆盖,则会在下一次获取该值的时候,会在后台异步去刷新缓存
刷新时只有一个请求回源取数据,其他请求会阻塞(block)在一个固定时间段,如果在该时间段内没有获得新值则返回旧值。
@Test
public void refresh() throws InterruptedException, ExecutionException {
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(3)
.concurrencyLevel(Runtime.getRuntime().availableProcessors())
.refreshAfterWrite(3, TimeUnit.SECONDS)
.build(new CacheLoader<Integer, Integer>() {
@Override
public Integer load(Integer key) throws Exception {
return sourceMap.get(key);
}
});
cache.get(1);
System.out.println("第一次取值: " + cache.getIfPresent(1));
sourceMap.put(1, 10);
Thread.sleep(5000);
System.out.println("第二次取值: " + cache.getIfPresent(1));
}
应用场景:accesstoken token失效 从公网拿token 采用更新锁定
GuavaCache高级实战之疑难问题
GuavaCache会oom(内存溢出)吗
会,当我们设置缓存永不过期(或者很长),缓存的对象不限个数(或者很大)时,比如:
Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(100000, TimeUnit.SECONDS)
.build();
解决方案:缓存时间设置相对小些,使用弱引用方式存储对象
GuavaCache缓存到期就会立即清除吗
不是的,GuavaCache是在每次进行缓存操作的时候,如get()或者put()的时候,判断缓存是否过期。
一个如果一个对象放入缓存以后,不在有任何缓存操作(包括对缓存其他key的操作),那么该缓存不
会主动过期的。
GuavaCache如何找出最久未使用的数据
用accessQueue,这个队列是按照LRU的顺序放的缓存对象(ReferenceEntry)的,会把访问过的对象放在队列的最后。
并且可以很方便的更新和删除链表中的节点,因为每次访问的时候都可能需要更新链表,放入到链表的尾部。
这样,每次从access中拿出的头结点就是最久未使用的。
对应的writeQueue用来保存最久未更新的缓存队列,实现方式和accessQueue一样。
其他比较好的文章推荐: