13 | Guava Cache 原理

GuavaCache作为优秀的本地缓存解决方案,提供容量、时间及索引回收机制,适用于内存空间换取速度提升的场景,如报表数据预加载。支持CacheLoader与Callable加载方式,具备多种缓存回收策略,包括按容量、定时及基于引用回收。

Guava Cache

Guava Cache 是一种非常优秀的本地缓存解决方案,提供了基于容量,时间和索引的缓存回收方式。

为什么要使用本地缓存

  • 相对于本地 I/O 来说:速度快、效率高;
  • 相对于 Redis 来说:Redis 虽然优秀,但受限于网卡等原因,远水解不了近渴;

Guava Cache 的使用场景

  • 愿意消耗一些内存空间来提升速度,比如做报表的时候,把报表用到的数据,从 MySQL 中查出来,放到 Guava Cache 中,然后在内存中做计算,完了在返回给客户端;
  • 预料到某些键会被多次查询;
  • 缓存中存放的数据总量不会超出内存容量;

Guava Cache 缓存的两种加载方式

都是利用 CacheBuilder 的 builder 模式来构建。

CacheLoader
  • CacheLoader 是按 key 统一加载,所有取不到的数据,则统一智行一种 load 逻辑;
Callable
  • Callable 方法允许在 get 的时候指定 key;

如何使用 Guava Cache ?

  1. 设置缓存容量;
  2. 设置超时时间:缓存什么时候过期,过期以后数据怎么处理;
  3. 提供移除监听器:在数据移除的时候,会收到通知;
  4. 提供缓存加载器:缓存是开机加载,还是手动加载,还是自动加载是由缓存加载器设置的;
  5. 构建缓存:直接使用代码的方式构建缓存;

Guava Cache 中一些关键的类

CacheBuilder
  • 构建缓存的入口,指定缓存配置参数并初始化本地缓存;
  • CacheBuilder 在 build 方法中,会把前面设置的参数,全部传递给 LocalCache,它自己实际不参与任何计算;这种初始化参数的方式值得借鉴,代码简洁宜易读;
CacheLoader | 抽象类
  • 用户从数据源加载数据,定义 load、reload、loadAll 等操作;
Cache | 接口
  • 定义 put、get、invalidate 等操作,这里只有缓存增删改的操作,没有数据加载的操作;
AbstractCache | 抽象类
  • 实现 Cache 接口;
  • 其中批量操作都是循环执行单词行为,而单次行为都没有具体定义;
LoadingCache | 接口
  • 继承自 Cache;
  • 定义 get、getUnchecked、getAll 等操作,这些操作都会从数据源 load 数据;
AbstractLoadingCache | 抽象类
  • 继承自 AbstractCache,实现 LoadingCache 接口;
LocalCache | 类
  • 整个 Guava Cache 的核心类,包含了 Guava Cache 的数据结构,以及基本的缓存操作方法;
LocalManualCache | LocalCache 静态内部类,实现 Cache 接口
  • 其内部的增删改操作,全部调用成员变量 localCache(LocalCache)类型的相应方法;

Guava Cache 缓存回收的三种方式

按容量回收
  • maximumSize(long):当缓存中的操作超过指定值,会把超过容量的数据回收掉;
定时回收
  • expireAfterAccess(long, TimeUnit):缓存项在执行时间内没有被读或写,则回收;
  • expireAfterWrite(long, TimeUnit):缓存项在给定的时间内没有被写访问,则回收;
基于引用回收
  • CacheBuilder.weakKeys():使用弱引用存储 key;
  • CacheBuilder.weakValues():使用弱引用存储 value;
  • CacheBuilder.softValues():使用软引用存储 value;

Guava Cache 的缓存清除

  • 常规 remove、clear 等操作;
  • Removal Listener:通过 CacheBuilder.removalListener(RemovalListener) 方法添加一个监听器,当数据清除的时候,RemovalListener 会收到一个通知,通知中指定要清除的 key 和 value;

Guava Cache 的缓存信息统计

  • 通过 CacheBuilder.recordStats() 方法,可以对 Guava Cache 进行缓存统计工作,可以统计一下信息:
    • hitRate():返回查询的命中率;
    • averageLoadPenalty():加载新值时的平均耗时;
    • evictionCount():缓存回收的数量;
<think> 我们正在讨论Guava Cache的expireAfterAccess方法原理。根据用户的问题,需要详细解释该方法的内部工作机制。 注意:用户要求参考站内引用,但这里没有提供具体的引用文献,因此我将根据Guava Cache的公开文档和常见实现原理进行解释。 首先,expireAfterAccess是Guava Cache中设置条目在指定时间内未被访问则自动过期的一种机制。这里的“访问”包括读和写操作。 原理要点: 1. Guava Cache使用类似ConcurrentHashMap的结构存储数据,但增加了过期和淘汰机制。 2. 对于每个缓存条目,记录一个“访问时间”(由系统ticker提供,通常是系统时钟,但可替换)。 3. 当进行任何访问操作(get或put)时,会更新该条目的访问时间。 4. 缓存维护一个访问队列(通常是双向链表),用于实现基于访问顺序的过期。在Guava Cache中,这是通过一个WriteOrderQueue(用于写顺序,但expireAfterAccess也会使用类似的机制)或者一个独立的访问队列来实现的。实际上,在Guava Cache的实现中,expireAfterAccess和expireAfterWrite使用同一个队列(写队列),但expireAfterAccess在访问时会更新写入时间(因此也更新了在写队列中的位置)[^1]。 5. 过期清理的触发:在访问缓存(get、put等)时,会触发清理(惰性清理);此外,还可以配置定期清理(通过设置refreshAfterWrite等,但expireAfterAccess本身不直接触发定期清理,而是依赖于写操作或读操作触发清理)。 具体步骤: - 当创建缓存时,如果指定了expireAfterAccess(duration, timeUnit),则会为每个条目记录一个访问时间(初始值为当前时间)。 - 每当有读操作(get)或写操作(put)更新该条目时,访问时间会被更新为当前时间。 - 缓存维护一个按访问时间排序的队列(实际上,在Guava Cache中,expireAfterAccess与expireAfterWrite共享一个写队列,但每次访问都会更新写入时间,从而也相当于更新了访问时间[^2])。 - 当需要清理过期条目时(例如,在每次写操作后,或者读操作时可能会触发清理),会检查队列头部的元素(即最早访问的条目)是否过期(当前时间 - 访问时间 > duration)。如果过期,则逐出该条目。 注意:由于惰性清理机制,缓存并不保证在过期后立即删除条目,而是在后续的访问中逐步清理。 另外,在Guava Cache中,这种基于访问时间的过期策略与基于写入时间的过期策略(expireAfterWrite)是相互独立的,可以同时设置。 下面是一个简单的代码示例(仅用于展示概念,实际实现更复杂): 假设有一个缓存条目类: ```java class CacheEntry<V> { V value; long lastAccessTime; // 最后访问时间 } ``` 在get操作时: ```java public V get(K key) { CacheEntry<V> entry = map.get(key); if (entry != null) { long now = ticker.read(); if (now - entry.lastAccessTime > expireDuration) { // 已过期,移除 map.remove(key); return null; } else { // 更新访问时间 entry.lastAccessTime = now; // 并将该条目移动到队列的尾部(表示最近访问)[实际上Guava使用写队列,这里简化] return entry.value; } } return null; } ``` 但是,实际Guava Cache的实现要复杂得多,因为它使用了分段锁(类似于ConcurrentHashMap)来支持高并发,并且维护了一个访问队列(写队列)来按顺序处理过期。 引用说明: [^1]: Guava Cache使用一个队列(通常称为写队列)来维护条目的写入顺序,而对于expireAfterAccess,每次访问都会更新写入时间(因为访问被视为一次写入队列的操作,从而更新了在队列中的位置)。但实际上,在Guava Cache中,expireAfterAccess和expireAfterWrite共享同一个队列(writeQueue),因为每次访问(读或写)都会触发一次写操作(更新访问时间)[参考Guava源码中的LocalCache类]。 [^2]: 在Guava Cache中,每个缓存条目都有一个writeTime字段,用于记录写入时间(包括初始写入和后续访问更新的时间)。expireAfterAccess的过期检查就是基于这个时间字段。 相关问题: 1. expireAfterAccess与expireAfterWrite有什么区别? 2. Guava Cache的过期清理是实时的吗?如果不是,那它是如何触发的? 3. 如何配置Guava Cache以同时使用expireAfterAccess和expireAfterWrite? 4. 在并发环境下,Guava Cache如何处理expireAfterAccess的更新和清理? </think> ### Guava Cache的`expireAfterAccess`方法原理 `expireAfterAccess`是Google Guava Cache中用于控制缓存条目基于**访问时间**自动过期的核心机制,其工作原理可分为以下层次: #### 1. **基础行为** - 当为缓存配置`expireAfterAccess(duration, timeUnit)`后,任何条目在最后一次访问(读或写)后超过指定时间未被触碰,则自动失效。 - 访问行为包括: - `get()`读取操作 - `put()`写入操作 - `getIfPresent()`查询操作 - 显式调用`asMap()`后的访问[^1] #### 2. **底层实现机制** - **时间记录**:每个缓存条目维护一个`accessTime`时间戳,记录最后一次访问的纳秒级时间(通过`System.nanoTime()`获取)。 - **惰性清理(Lazy Eviction)**: - 过期检查不依赖后台线程,而是在以下操作时触发: - 缓存写入(`put()`) - 缓存读取(`get()`) - 显式调用`cleanUp()`方法 - 例如执行`cache.get(key)`时,会检查该条目是否满足: $$ \text{currentTime} - \text{accessTime} > \text{duration} $$ 若成立则直接删除该条目[^2]。 - **数据结构优化**: - 使用**并发哈希表+双向链表**(类似LinkedHashMap)按访问顺序排序。 - 链表头部存放最久未访问条目,尾部存放最近访问条目,实现$O(1)$时间复杂度的过期检查[^3]。 #### 3. **并发控制** - 基于分片锁(Segment Locking): - 缓存被分为多个Segment(默认16个),每个Segment独立加锁。 - 更新`accessTime`时仅需获取对应Segment锁,避免全局锁竞争。 - 时间戳更新使用`volatile`变量,保证多线程可见性。 #### 4. **与其他过期策略的差异** | **策略** | `expireAfterAccess` | `expireAfterWrite` | |----------------------|-----------------------------------|-----------------------------------| | 触发条件 | 读/写操作后超时 | 写入操作后超时 | | 适用场景 | 低频访问的冷数据 | 需要定期刷新的数据(如配置信息) | | 链表类型 | 访问顺序链表 | 写入顺序链表 | ### 示例代码 ```java Cache<String, Data> cache = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) // 10分钟未访问则过期 .build(); // 触发访问时间更新 cache.put("key1", data); // 写入更新accessTime Data val = cache.get("key1"); // 读取更新accessTime ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值