一、简介
Guava cache 是一个全内存本地缓存。它拥有并发策略、缓存过期机制、缓存移除机制、缓存统计等功能.
缓存的优势:
1、减少网络传输的开销
2、减少数据序列化和反序列化
3、加快了访问速度(与数据库、文件系统相比)。
缓存的使用场景:
1、缓存全量数据
2、缓存热点数据
二、基本用法
1、缓存加载
这两种方法都实现一种逻辑:从缓存中取key的值,如果该key已经缓存过,直接返回缓存中的值,
如果没有缓存该key,可以通过某个方法来获取这个值。
-
CacheLoader
-
Callable
2、策略分析
-
expireAfterAccess 读写缓存后多久过期
-
expireAfterWrite 写缓存后多久过期
并发时的特性:(无论缓存中是否存在数据)如果当其他线程在加载数据,当前线程会一直阻塞等待其它线程加载完成数据
缺点:导致缓存穿透
-
refreshAfterWrite 写缓存后多久刷新数据,只阻塞加载数据线程,其他线程返回旧值
并发时的特性:
a、缓存中没有数据时(初始化缓存数据时),如果其它线程在加载数据的时候,当前线程会一直阻塞等待其它线程加载数据完成。
b、缓存中有数据(已经初始化缓存中的数据时),如果当前线程正在加载数据,其它线程返回旧数据。
缺点:导致数据过旧的问题
3、缓存回收
-
基于容量回收
CacheBuilder.maxmumSize(long)
-
基于时间回收
CacheBuilder.expireAfterAccess()
CacheBuilder.expireAfterWrite()
-
基于引用回收
软引用和弱引用
CacheBuilder.weakKeys()
当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。
CacheBuilder.weakValues()
CacheBuilder.softValues()
软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。使用软引用时需要考虑性能影响。使用软引用值的缓存同样用==而不是equals比较值。
引用类型
描述
回收时间
用途
备注
强引用
程序中随处可见
永不回收
普通对象引用
软引用
描述对象有用但不是必须的
内存不足回收
缓存对象
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,java虚拟机就会把这个软引用加入到与之关联的引用队列中
弱引用
描述对象不是非必须的
jvm垃圾回收
缓存对象
4、其它
-
显示清除
任何时候,你都可以显式地清除缓存项,而不是等到它被回收:
-
清除所有缓存项:Cache.invalidateAll()
三、缓存回收原理及实现
1、什么是LRU算法
LRU(Least recently used,最近最少使用)是核心思想是基于“如果数据最近被访问过,那将来被访问的几率也更高”。
1.1:新数据插入到链表的头部。
1.2:每当缓存命中(即缓存数据被访问),则将数据移到链表头部。
1.3: 当链表满的时候,将链表尾部的数据丢弃。
2、LocalCache数据结构
名称 | 类型 | 作用 |
---|---|---|
segments | Segment<k,V>[] | 实现ReentrantLock锁,减少锁的粒度,提高并发度。好处:分段锁很好保证并发读写的效率,因此支持非阻塞的读和不同段之间的并发写 |
table | AtomicReferenceArray<ReferenceEntry<K,V> | 存放键值对的地方 |
referenceEntry | ReferenceEntry<K,V> | 封装键值对 |
keyReferenceQueue | ReferenceQueue<K> | 已经被GC,需要内部清理key引用队列 |
valueReferenceQueue | ReferenceQueue<V> | 已经被GC,需要内部清理value引用队列 |
recencyQueue | Queue<ReferenceEntry<K,V>> | 记录当entries被访问时,去更新accessQueue中顺序。在segment中当segment上限值或是写操作发生会去更新accessQueue顺序,同时清空recencyQueue。 |
writeQueue | Queue<ReferenceEntry<K,V>> | 按照写入时间进行排序的元素队列,写入元素时会把它加入队列的队尾 |
accessQueue | Queue<ReferenceEntry<K,V>> | 按照访问时间进行排序的元素队列,访问或是写入元素时会把它加入到队列的队尾。 |
3、segment如何清理(evict) entry
evict方式
使用cacheBuilder构建的缓存不会“自动”执行清理和回收工作,也不会在某个缓存项过期后马上清理。
相反,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话。
理由:如果guava使用开启一个后台线程每隔一段时间来扫描一次table以evict哪些已经expire的entry,
这样的增加资源的消耗。
evict对象
基于引用回收
keyReferenceQueue
valueReferenceQueue
基于时间回收
writeQueue
accessQueue
evict流程
基于引用和时间回收策略
put开始时
get结束之前
基于容量回收策略(LRU)
put结束之前
前提条件:在设置maxmumSize或maximumWeight时,才会进行该操作。
weight的作用
1. 对weight值为0时,在计算因为size limit而evict是忽略该Entry(它可以通过其他机制evict)。
2. 如果设置了maximumWeight值,则当Cache中weight和超过了该值时,就会引起evict操作。
四、总结
设计缓存系统中过期机制时,可以考虑在读和写操作进行清理操作,从而提供效率。
参考文档:
【1】、google guava cache缓存基本使用讲解
【3】、CachesExplained
【4】、自定义LRU算法的缓存实现
【6】、Java Cache系列之Guava Cache实现详解
【7】、GuavaCache
【9】、Java的引用类型
【10】、Guava Cache 的缓存管理与使用