最全Guava Cache实战—从场景使用到原理分析,实战解析

总结

总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。

如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

//最大 key 个数

.maximumSize(100)

//移除监听器

.removalListener(removalListener)

//设置 cache 中的数据在写入之后的存活时间为10秒

.expireAfterWrite(10, TimeUnit.SECONDS)

//构建 cache 实例

.build(new CacheLoader<Long, TestDemo>() {

@Override

public TestDemo load(Long id) throws Exception {

// 读取 db 数据

}

});

RemovalListener<String, String> removalListener = new RemovalListener<String, String>() {

public void onRemoval(RemovalNotification<id, TestDemo> removal) {

System.out.println(“[” + removal.getKey() + “:” + removal.getValue() + removal.getCause() + “] is evicted!”);

}

};

  • 3、 如果缓存加载方法时出现了异常,那么下一次是缓存异常还是正常数据?

public class TestDemoImpl implements TestDemo {

int time;

private final LoadingCache<Long, TestDemo> demo;

this.demo1 = CacheBuilder.newBuilder()

.expireAfterWrite(duration, TimeUnit.HOURS)

.build(new CacheLoader<Long, TestDemo>() {

@Override

public TestDemo load(Long id) throws Exception {

// 查询 db

if (time++==0) {

throw new RunTimeException();

}

return result;

}

});

}

public static void main(String[] args) {

try {

try {

TestDemo testDemo = demoCache.getUnchecked(21L);

System.out.println(“第一次查询:” + testDemo);

} catch (Exception e) {

}

TestDemo testDemo1 = testDemo.getUnchecked(21L);

System.out.println(“第二次查询:” + testDemo1);

} catch (Exception e) {

e.printStackTrace();

}

}

  • 结论:第二次返回正常数据

guava 使用踩坑

  • 1、使用 guava cache 时避免使用 weakkeys。weakkeys 对 key 的命中规则是 ==,如果使用非基本类型,会因为 key 判断不相等导致缓存无法命中。

  • 2、仅仅需缓存元数据本身,不要缓存其关系,否则造成笛卡尔积。如缓存的数据由A,B,C三张表的维度组成,缓存关系会导致A X B X C的数据量,如果缓存元数据,则缓存的数据量仅为 A+B+C;

  • 3、使用缓存前必须预估缓存的数据大小,并设置缓存的数量或大小。如果不设置过期方式的话,也不设置大小,缓存数据将无法回收,会引起 OOM

如何清理 Guava 缓存

在日常开发过程中,最常见的问题是,如何清理 Guava 缓存,下面介绍两种清理方式

手动清理

通过 rest 后门等手动调用

// 获取最新 db 数据更新缓存

testDemoCache.refresh(key);

// 清理 guava cache 缓存

estDemoCache.invalidateAll();

自动清理缓存

手动清理缓存,人力成本较高,现在有一种方案是,在新增/修改数据后,通过 MQ 广播来实现所有机器自动清理缓存。或通过 canal 等 binlog 中间件监听 db 变更来清理缓存。

Guava cache 原理解析

Guava cache 继承了 ConcurrentHashMap 的思路,使用多个 segments 方式的细粒度锁,在保证线程安全的同时,支持高并发场景需求。下面介绍它的底层实现,分析其性能优异的原因

几个重要的组件

1、CacheBuilder 缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。采用 Builder 设计模式提供了设置好各种参数的缓存对象。 2、LocalCache 数据结构。缓存核心类 LocalCache 数据结构与 ConcurrentHashMap 很相似,由多个 segment 组成,且各 segment 相对独立,互不影响,所以能支持并行操作,每个 segment 由一个 table 和若干队列组成。缓存数据存储在 table 中,其类型为AtomicReferenceArray。

在这里插入图片描述

上图为 Guava cache 底层的数据结构图,下表对其结构做了说明

| 序号 | 数据结构 | 特点 | 详解 |

| — | — | — | — |

| 1 | Segment<K, V>[] segments | Segment 继承于ReetrantLock,减小锁粒度,提高并发效率 | |

| 2 | AtomicReferenceArray<ReferenceEntry<K, V>> table | 类似于HasmMap 中的table 一样,相当于 entry 的容器 | |

| 3 | ReferenceEntry<K, V> referenceEntry | 基于引用的Entry,其实现类有弱引用 Entry,强引用 Entry 等 | Cache 由多个 Segment 组成,而每个 Segment 包含一个ReferenceEntry 数组,每个 ReferenceEntry 数组项都是一条 ReferenceEntry 链,且一个 ReferenceEntry 包含key、hash、valueReference、next 字段。除了在ReferenceEntry 数组项中组成的链,在一个 Segment中,所有 ReferenceEntry 还组成 access 链(accessQueue)和 write 链(writeQueue)。ReferenceEntry 可以是强引用类型的 key,也可以WeakReference 类型的 key,为了减少内存使用量,还可以根据是否配置了 expireAfterWrite、expireAfterAccess、maximumSize 来决定是否需要 write 链和 access 链确定要创建的具体 Reference:StrongEntry、StrongWriteEntry、StrongAccessEntry、StrongWriteAccessEntry等 |

| 4 | ReferenceQueue keyReferenceQueue | 已经被 GC,需要内部清理的键引用队列 | |

| 5 | ReferenceQueue valueReferenceQueue | 已经被 GC,需要内部清理的值引用队列 | 因为 Cache 支持强引用的 Value、SoftReference Value 以及 WeakReference Value,因而它对应三个实现类:StrongValueReference、SoftValueReference、WeakValueReference。为了支持动态加载机制,它还有一个 LoadingValueReference,在需要动态加载一个 key的值时,先把该值封装在 LoadingValueReference 中,以表达该 key 对应的值已经在加载了,如果其他线程也要查询该 key 对应的值,就能得到该引用,并且等待改值加载完成,从而保证该值只被加载一次,在该值加载完成后,将 LoadingValueReference 替换成其他 ValueReference类型。ValueReference 对象中会保留对 ReferenceEntry 的引用,这是因为在 Value 因为 WeakReference、SoftReference 被回收时,需要使用其 key 将对应的项从Segment 的 table 中移除 |

| 6 | Queue<ReferenceEntry<K, V>> recencyQueue | 记录升级可访问列表清单时的entries ,当segment 上达到临界值或发生写操作时该队列会被清空 | |

| 7 | Queue<ReferenceEntry<K, V>> writeQueue | 按照写入时间进行排序的元素队列,写入一个元素时会把它加入到队列尾部 | 为了实现最近最少使用算法,Guava Cache 在 Segment 中添加了两条链:write 链(writeQueue)和 access 链(accessQueue),这两条链都是一个双向链表,通过ReferenceEntry 中的 previousInWriteQueue、nextInWriteQueue 和 previousInAccessQueue、nextInAccessQueue 链接而成,但是以 Queue 的形式表达。WriteQueue 和 AccessQueue 都是自定义了 offer、add(直接调用 offer)、remove、poll 等操作的逻辑,对offer(add)操作,如果是新加的节点,则直接加入到该链的结尾,如果是已存在的节点,则将该节点链接的链尾;对 remove 操作,直接从该链中移除该节点;对 poll操作,将头节点的下一个节点移除,并返回 |

| 8 | Queue<ReferenceEntry<K, V>> accessQueue | 按照访问时间进行排序的元素队列,访问(包括写入)一个元素时会把它加入到队列尾部 | |

guava常用接口

/**

  • 该接口的实现被认为是线程安全的,即可在多线程中调用

  • 通过被定义单例使用

*/

public interface Cache<K, V> {

/**

  • 通过key获取缓存中的value,若不存在直接返回null

*/

V getIfPresent(Object key);

在这里插入图片描述

上图为方法 getIfPresent 执行流程图

/**

  • 通过 key 获取缓存中的 value,若不存在就通过 valueLoader 来加载该 value

  • 整个过程为 “if cached, return; otherwise create, cache and return”

  • 注意 valueLoader 要么返回非 null 值,要么抛出异常,绝对不能返回 null

*/

V get(K key, Callable<? extends V> valueLoader) throws ExecutionException;

在这里插入图片描述

上图为 get 方法执行流程图

/**

  • 添加缓存,若key存在,就覆盖旧值

*/

void put(K key, V value);

复制代码

在这里插入图片描述

上图为 put 方法执行流程图

/**

  • 删除该key关联的缓存

*/

void invalidate(Object key);

/**

  • 删除所有缓存

*/

void invalidateAll();

/**

  • 执行一些维护操作,包括清理缓存

*/

void cleanUp();

}

在这里插入图片描述

上图为移除缓存的流程图

缓存回收

Guava Cache 提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。 基于容量的方式内部实现采用 LRU 算法,基于引用回收很好的利用了 Java 虚拟机的垃圾回收机制。

1、基于容量的回收(size-based eviction)

如果要规定缓存项的数目不超过固定值,只需使用 CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。在缓存项的数目达到限定值之前,缓存就可能进行回收操作,通常来说,这种情况发生在缓存项的数目逼近限定值时。

最后

做任何事情都要用心,要非常关注细节。看起来不起眼的、繁琐的工作做透了会有意想不到的价值。
当然要想成为一个技术大牛也需要一定的思想格局,思想决定未来你要往哪个方向去走, 建议多看一些人生规划方面的书籍,多学习名人的思想格局,未来你的路会走的更远。

更多的技术点思维导图我已经做了一个整理,涵盖了当下互联网最流行99%的技术点,在这里我将这份导图分享出来,以及为金九银十准备的一整套面试体系,上到集合,下到分布式微服务

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

分布式微服务**

[外链图片转存中…(img-GciBSicu-1715554927027)]

[外链图片转存中…(img-omVXrJnl-1715554927028)]

[外链图片转存中…(img-lcOrkfER-1715554927029)]

[外链图片转存中…(img-C0aIeRIw-1715554927029)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值