Google Guava 缓存工具使用详解

2023-12-05


缓存工具

Guava提供了Cache接口和相关的类来支持缓存功能,它提供了高性能、线程安全的内存缓存,可以用于优化应用程序的性能。

特点:

  • 自动回收过期数据
  • 支持缓存项的定时回收
  • 支持缓存项的最大数量限制
  • 支持缓存项的大小限制
  • 支持缓存项的权重限制,简化开发者实现基于容量的限制
  • 提供了统计信息

相关接口类:

接口/类 描述
Cache<K, V> 接口表示一种能够存储键值对的缓存结构
LoadingCache<K, V> 是 Cache 接口的子接口,用于在缓存中自动加载缓存项
CacheLoader<K, V> 在使用 LoadingCache 时提供加载缓存项的逻辑
CacheBuilder 用于创建 Cache LoadingCache 实例的构建器类
CacheStats 用于表示缓存的统计信息,如命中次数、命中率、加载次数、存储次数等
RemovalListener<K, V> 用于监听缓存条目被移除的事件,并在条目被移除时执行相应的操作

使用示例:

    public static void main(String[] args) throws Exception {
        // 创建Cache实例
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .initialCapacity(2)                                         // 设置初始容量
                .concurrencyLevel(4)                                        // 设置并发级别
                .maximumSize(5)                                             // 设置最大容量
//                .maximumWeight(1000)                                        // 设置最大权重
//                .weigher((Weigher<String, String>) (k, v) -> v.length())    // 设置权重计算器
                .expireAfterWrite(Duration.ofSeconds(3))                    // 写入后3秒过期
                .expireAfterAccess(Duration.ofSeconds(20))                  // 访问后20秒过期
                .refreshAfterWrite(Duration.ofSeconds(10))                  // 写入后自动刷新,3秒刷新一次
                .recordStats()                                              // 开启统计信息记录
                .removalListener(notification -> {                          // 设置移除监听
                    // 缓存Key被移除时触发
                    String cause = "";
                    if (RemovalCause.EXPLICIT.equals(notification.getCause())) {
                        cause = "被显式移除";
                    } else if (RemovalCause.REPLACED.equals(notification.getCause())) {
                        cause = "被替换";
                    } else if (RemovalCause.EXPIRED.equals(notification.getCause())) {
                        cause = "被过期移除";
                    } else if (RemovalCause.SIZE.equals(notification.getCause())) {
                        cause = "被缓存条数超上限移除";
                    } else if (RemovalCause.COLLECTED.equals(notification.getCause())) {
                        cause = "被垃圾回收移除";
                    }
                    System.out.println(DateUtil.formatDateTime(new Date()) + " Key: " + notification.getKey() + " 移除了, 移除原因: " + cause);
                })
                .build(new CacheLoader<String, String>() {                  // 设置缓存重新加载逻辑
                    @Override
                    public String load(String key) {
                        // 重新加载指定Key的值
                        String newValue = "value" + (int)Math.random()*100;
                        System.out.println(DateUtil.formatDateTime(new Date()) + " Key: " + key + " 重新加载,新value:" + newValue);
                        return newValue;
                    }
                });
        // 将数据放入缓存

        cache.put("key0", "value0");
        cache.invalidate("key0");

        cache.put("key1", "value1");
        cache.put("key1", "value11");

        cache.put("key2", "value22");
        cache.put("key3", "value3");
        cache.put("key4", "value4");
        cache.put("key5", "value5");
        cache.put("key6", "value6");
        cache.put("key7", "value7");
        cache.put("key8", "value8");

        while (true) {
            // 获取数据
            System.out.println(DateUtil.formatDateTime(new Date()) + " get key1 value: " + cache.get("key1"));
            // 统计信息
            System.out.println(DateUtil.formatDateTime(new Date()) + " get stats: " + cache.stats());
            Thread.sleep(1000);
        }
    }

打印日志:

2023-11-24 15:48:17 Key: key0 移除了, 移除原因: 被显式移除
2023-11-24 15:48:17 Key: key1 移除了, 移除原因: 被替换
2023-11-24 15:48:17 Key: key1 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key2 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key3 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key1 重新加载,新value:value0
2023-11-24 15:48:17 Key: key4 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 get key1 value: value0
2023-11-24 15:48:17 get stats: CacheStats{hitCount=0, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:18 get key1 value: value0
2023-11-24 15:48:18 get stats: CacheStats{hitCount=1, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:19 get key1 value: value0
2023-11-24 15:48:19 get stats: CacheStats{hitCount=2, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:20 Key: key5 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key6 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key7 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key8 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key1 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key1 重新加载,新value:value0
2023-11-24 15:48:20 get key1 value: value0
2023-11-24 15:48:20 get stats: CacheStats{hitCount=2, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:21 get key1 value: value0
2023-11-24 15:48:21 get stats: CacheStats{hitCount=3, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:22 get key1 value: value0
2023-11-24 15:48:22 get stats: CacheStats{hitCount=4, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:23 Key: key1 移除了, 移除原因: 被过期移除
2023-11-24 15:48:23 Key: key1 重新加载,新value:value0
2023-11-24 15:48:23 get key1 value: value0
2023-11-24 15:48:23 get stats: CacheStats{hitCount=4, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
2023-11-24 15:48:24 get key1 value: value0
2023-11-24 15:48:24 get stats: CacheStats{hitCount=5, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
2023-11-24 15:48:25 get key1 value: value0
2023-11-24 15:48:25 get stats: CacheStats{hitCount=6, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
......

Cache接口

Cache接口是Guava缓存的核心接口,定义了缓存的基本操作。

它是使用 CacheBuilder 创建的。它提供了基本的缓存操作,如 put、get、delete等方法。同时,Cache 还提供了诸如统计信息、缓存项的值获取方式、缓存项的失效、缓存项的回收等方法,可以满足大多数应用的需求。

主要方法:

方法 描述
V get(K key, Callable<? extends V> valueLoader) 根据键获取对应的缓存值,如果缓存中不存在该键,会使用 valueLoader 加载并存储该值。
V getIfPresent(K key) 根据键获取对应的缓存值,如果不存在则返回 null。
Map<K, V> getAllPresent(Iterable<?> keys) 获取多个键对应的缓存值的映射,如果缓存中不存在某个键,则该键不会出现在返回的映射中。
void put(K key, V value) 将键值对放入缓存中。如果键已经存在,则会替换对应的值。
void putAll(Map<? extends K, ? extends V> map) 将多个键值对添加到缓存中。
void invalidate(Object key) 根据键从缓存中移除条目。
void invalidateAll(Iterable<?> keys) 根据键集合移除多个条目。
void invalidateAll() 移除缓存中的所有条目。
long size() 返回缓存中的条目数。
CacheStats stats() 返回缓存的统计信息。
ConcurrentMap<K, V> asMap() 返回缓存的并发映射视图。
void cleanUp() 执行缓存的清理操作。

LoadingCache接口

LoadingCache 继承自 Cache 接口,它是一个带有自动加载功能的缓存接口。

在使用 LoadingCache 时,如果缓存中不存在所需的键值对,则会自动调用CacheLoader的加载方法进行加载,并将加载的结果存入缓存中。

主要方法:

方法 说明
get(K key) 根据指定的键检索值,如果键不存在,将调用 CacheLoader 进行加载并返回对应的值
getAll(Iterable<? extends K> keys) 根据给定的键集合批量检索值,并返回一个 Map 对象,对于已缓存的键将直接返回对应的值,对于未缓存的键将通过 CacheLoader 进行加载。
getUnchecked(K key) 获取指定键对应的值,如果缓存中不存在该键,则返回 null,不会触发CacheLoader 加载。
refresh(K key) 刷新指定键对应的值,即使用 CacheLoader 重新加载该键对应的值,并更新缓存。

CacheBuilder类

CacheBuilder类是用于创建Guava缓存的构建器。可以使用该类的newBuilder()方法创建一个构建器实例,并通过一系列方法设置缓存的属性,例如最大容量、过期时间等。最后可以通过build()方法构建一个Cache实例。

主要方法:

方法 说明
newBuilder() 创建一个新的 CacheBuilder 实例
from(CacheBuilderSpec spec) 根据给定的规范字符串创建一个 CacheBuilder 实例
from(String spec) 根据给定的规范字符串创建一个 CacheBuilder 实例
initialCapacity(int initialCapacity) 设置缓存的初始容量
concurrencyLevel(int concurrencyLevel) 设置并发级别,用于估计同时写入的线程数
maximumSize(long maximumSize) 设置缓存的最大容量
maximumWeight(long maximumWeight) 设置缓存的最大权重
weigher(Weigher<? super K1, ? super V1> weigher) 设置缓存的权重计算器
weakKeys() 使用弱引用存储缓存键(例如,键的引用没有被其他对象引用时,可以被垃圾回收)
weakValues() 使用弱引用存储缓存值(例如,值的引用没有被其他对象引用时,可以被垃圾回收)
softValues() 使用软引用存储缓存值(例如,当内存不足时,可以被垃圾回收)
expireAfterWrite(java.time.Duration duration) 设置写入后过期时间
expireAfterWrite(long duration, TimeUnit unit) 设置写入后过期时间
expireAfterAccess(java.time.Duration duration) 设置访问后过期时间
expireAfterAccess(long duration, TimeUnit unit) 设置访问后过期时间
refreshAfterWrite(java.time.Duration duration) 设置写入后自动刷新时间
refreshAfterWrite(long duration, TimeUnit unit) 设置写入后自动刷新时间
ticker(Ticker ticker) 设置用于衡量缓存时间的时钟源
removalListener(RemovalListener<? super K1, ? super V1> listener) 设置缓存条目移除监听器
recordStats() 开启缓存统计信息记录
build(CacheLoader<? super K1, V1> loader) 使用指定的 CacheLoader 构建缓存
build() 构建缓存,如果没有指定 CacheLoader,则需要使用 get 方法手动加载缓存项

部分方法详解:

  • initialCapacity: 设置缓存的初始容量

    这个方法将通过一个整数值设置缓存的初始大小。它是一个可选的方法,如果没有指定,缓存将采用默认的初始容量。

  • concurrencyLevel: 设置并发级别

    用于估计同时写入的线程数。这个方法将通过一个整数值设置并发级别,用于内部数据结构的调整,以提高并发写入的性能。它是一个可选的方法,缺省值为 4。

  • maximumSize: 设置缓存的最大容量

    这个方法将通过一个 long 类型的值设置缓存的最大容量。当缓存的条目数达到这个容量时,会触发缓存清除策略来移除一些条目以腾出空间。它是一个可选的方法,如果没有指定最大容量,缓存将不会有大小限制。

  • maximumWeight: 设置缓存的最大权重

    这个方法将通过一个 long 类型的值设置缓存的最大权重。权重可以根据缓存条目的大小计算,通常用于缓存对象大小不同的场景。当缓存的总权重达到这个值时,会触发缓存清除策略来移除一些条目以腾出空间。它是一个可选的方法,如果没有指定最大权重,缓存将不会有权重限制。

  • weigher: 设置缓存的权重计算器

    这个方法将通过一个实现了 Weigher 接口的对象设置缓存的权重计算器。通过权重计算器,可以根据缓存条目的键和值来计算它们的权重,以便在达到最大权重时触发缓存清除策略。它是一个可选的方法,如果没有设置权重计算器,缓存将不会有权重限制。

  • expireAfterWrite: 设置写入后过期时间

    这个方法通过一个 java.time.Duration 对象设置缓存条目的写入后过期时间。过期时间从最后一次写入条目开始计算。一旦超过指定的时间,条目将被认为是过期的并被清除。这是一个可选的方法,如果没有指定过期时间,条目将不会主动过期。

  • expireAfterAccess: 设置访问后过期时间

    这个方法通过一个 java.time.Duration 对象设置缓存条目的访问后过期时间。过期时间从最后一次访问条目开始计算。一旦超过指定的时间,条目将被认为是过期的并被清除。这是一个可选的方法,如果没有指定过期时间,条目将不会主动过期。

  • refreshAfterWrite: 设置写入后自动刷新时间

    这个方法通过一个 java.time.Duration 对象设置缓存条目的自动刷新时间。自动刷新时间从最后一次写入条目开始计算。一旦超过指定的时间,当条目被访问时,缓存将自动刷新该条目,即会调用 CacheLoader 的 load 方法重新加载该条目。这是一个可选的方法,如果没有设置自动刷新时间,条目将不会自动刷新。

  • recordStats(): 开启缓存统计信息记录

    这个方法用于开启缓存的统计信息记录功能。一旦开启,可以通过 Cache.stats() 方法获取缓存的统计信息,如命中率、加载次数、平均加载时间等。这是一个可选的方法,如果不开启统计信息记录,将无法获取缓存的统计信息。

注意事项:

  • maximumSize与maximumWeight不能同时设置

  • 设置maximumWeight时必须设置weigher

  • 当缓存失效后,refreshAfterWrite设置的写入后自动刷新时间不会再有用

    注意:expireAfterWrite、expireAfterAccess、refreshAfterWrite三个值的使用

  • 开启recordStats后,才进行统计

CacheLoader类

CacheLoader 可以被视为一种从存储系统(如磁盘、数据库或远程节点)中加载数据的方法。

CacheLoader 通常搭配refreshAfterWrite使用,在写入指定的时间周期后会调用CacheLoader 的load方法来获取并刷新为新值。

load 方法在以下情况下会被触发调用:

  • 当设置了refreshAfterWrite(写入后自动刷新时间),达到自动刷新时间时,会调用 load 方法来重新加载该键的值。

  • 调用 Cache.get(key) 方法获取缓存中指定键的值时,如果该键的值不存在,则会调用 load 方法来加载该键的值。

  • 调用 Cache.get(key, callable) 方法获取缓存中指定键的值时,如果该键的值存在,则直接返回;如果该键的值不存在,则会调用 callable 参数指定的回调函数来计算并加载该键的值。

  • 调用 Cache.getUnchecked(key) 方法获取缓存中指定键的值时,无论该键的值存在与否,都会调用 load 方法来加载该键的值。

需要注意的是,当调用 load 方法加载缓存值时,可能会发生 IO 操作或其他耗时操作,因此建议在加载操作中使用异步方式来避免阻塞主线程。另外,加载操作的实现要考虑缓存的一致性和并发性,避免多个线程同时加载同一个键的值。

CacheStats类

CacheStats 对象提供了诸如缓存命中率、加载缓存项数、缓存项回收数等统计信息的访问。

它可以通过 Cache.stats() 方法来获取,从而方便开发者监控缓存状态。

主要属性:

属性 描述
hitCount 缓存命中次数。表示从缓存中成功获取数据的次数
missCount 缓存未命中次数。表示从缓存中未能获取到数据的次数
loadSuccessCount 加载数据成功次数。表示通过 CacheLoader 成功加载数据的次数
loadExceptionCount 加载数据异常次数。表示通过 CacheLoader 加载数据时发生异常的次数
totalLoadTime 加载数据总耗时。表示通过 CacheLoader 加载数据的总时间
evictionCount 缓存项被移除的次数,只记录因空超过设置的最大容量而进行缓存项移除的次数

RemovalListener类

RemovalListener 用于在缓存中某个值被移除时执行相应的回调操作。

可以使用 CacheBuilder.removalListener() 方法为缓存设置 RemovalListener。

RemovalListener 的使用:

  1. 创建一个实现 RemovalListener 接口的类,实现 onRemoval 方法。这个方法会在缓存项被移除时被调用,接受两个参数: key 和 value。key 是被移除的缓存项的键,value 是被移除的缓存项的值。你可以根据需要在 onRemoval 方法中实现自定义的逻辑。

  2. 使用 CacheBuilder 的 removalListener 方法,将创建的 RemovalListener 对象传递给它。

  3. 缓存项被移除时,onRemoval 方法会自动被调用,方法会传入一个RemovalNotification 类型的参数,里面包含相应的 key 和 value等信息。你可以在这个方法中执行自定义的业务逻辑,例如日志记录、资源清理等操作。

    RemovalNotification:

    RemovalNotification 是 Guava 中用于表示缓存项被移除的通知的类。当在 Guava Cache 中注册了 RemovalListener 后,RemovalNotification 对象会在缓存项被移除时传递给 RemovalListener 的 onRemoval 方法。

    RemovalNotification 包含了有关被移除缓存项的一些重要信息,例如键、值以及移除原因。下面是 RemovalNotification 类中常用的属性和方法:

    • getKey():获取被移除的缓存项的键。

    • getValue():获取被移除的缓存项的值。

    • getCause():获取移除原因,它是一个枚举类型RemovalCause,表示缓存项被移除的原因。

      RemovalCause的可选值:

      • EXPLICIT:条目被显式删除,例如通过调用 Cache.invalidate(key) 方法。
      • REPLACED:条目被替换,例如通过调用 Cache.put(key, value) 方法重复放入相同的键。
      • EXPIRED:缓存条目由于达到了指定的过期时间而被移除。
      • SIZE:缓存条目由于超过了指定的大小限制而被移除。
      • COLLECTED:缓存条目被垃圾回收移除。这是在启用了缓存值的弱引用或软引用时发生的。

    使用 RemovalNotification 可以让你在缓存项被移除时获取相关信息,并根据移除原因采取适当的处理措施。例如,你可以根据移除原因记录日志、执行清理操作、发送通知等。这样能够增强缓存的功能和可观察性。

注意事项:

  • RemovalListener 的 onRemoval 方法会在移除操作发生时同步调用,因此请确保不要在此方法中做耗时的操作,以免阻塞缓存的性能。
  • 如果在缓存移除过程中抛出任何异常,它将被捕获并记录,不会影响缓存的正常运行。
  • 需要注意的是, RemovalListener 只会在通过 Cache 的操作(如 invalidate、invalidateAll、put 进行替换)触发移除时被调用,并不会在缓存项因为过期失效而自动移除时被调用

使用 RemovalListener 可以方便地在缓存项被移除时执行一些自定义的操作,例如清理相关资源、更新其他缓存或发送通知等。根据实际需要,合理利用 RemovalListener 可以增强缓存的功能和灵活性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Google Guava 缓存工具使用详解 的相关文章

随机推荐