谷歌Guava LoadingCache介绍

2024-08-07 11:13:14 浏览数 (2)

  在工作中,加Cache是非常常见的一种性能优化手段,操作系统底层、计算机硬件层为了性能优化加了各种各样的Cache,当然大多数都是对应用层透明的。但如果你想在应用层加Cache的话,可能就需要你自己实现了。

  其实在Java环境下,Cache有各种各样的选择,比如最初级的你可以直接用HashMap实现一个Cache,不过你得自己关注下数据加载和淘汰的策略。更高级的有像spring-cache,代码都不需要改,只需要简单加几个注解就可以实现对关键数据的缓存,相当方便(后续我也会出一篇博客介绍下spring-cache)。 今天我们要介绍的是谷歌guava包中的LoadingCache, 也是功能完善,简单好用。

  LoadingCache是Guava包中提供一个一种本地Cache,本地Cache的优势就是没有网络IO,速度快。但劣势也很明显,Cache容量受限于本地内存大小,Cache中的数据没法共享。所以它就只适合少量热点数据的缓存,其使用方法也很简单,我们拿maven为例,你只需要添加一下Maven依赖即可引入guava包:

代码语言:javascript复制
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>31.1-jre</version>
</dependency>

使用代码也非常简单,如下:

代码语言:javascript复制
    private static LoadingCache<String, String> cache =
            CacheBuilder.newBuilder()
                        // 初始化容量
                        .initialCapacity(4)
                        // 缓存池大小,在缓存数量到达该大小时, Guava开始回收旧的数据
                        .maximumSize(8)
                        // 设置时间对象没有被读/写访问则对象从内存中删除(在另外的线程里面不定期维护)
                        .expireAfterAccess(5, TimeUnit.SECONDS)
                        // 设置缓存在写入之后 设定时间 后失效
                        .expireAfterWrite(5, TimeUnit.SECONDS)
                        // 数据被移除时的监听器, 缓存项被移除时会触发执行
                        .removalListener((RemovalListener<String, String>) rn -> {
                            System.out.println(String.format("数据key:%s value:%s 因为%s被移除了", rn.getKey(), rn.getValue(),
                                    rn.getCause().name()));
                        })
                        // 开启Guava Cache的统计功能
                        .recordStats()
                        // 数据写入后被多久刷新一次
                        .refreshAfterWrite(5, TimeUnit.SECONDS)
                        // 数据并发级别
                        .concurrencyLevel(16)
                        // 当缓存中没有数据时的数据加载器
                        .build(new CacheLoader<String, String>() {
                            @Override
                            public String load(String key) throws Exception {
                                return key   "_"   System.currentTimeMillis();
                            }
                        });

  然后我们就可以直接在代码的其他地方用cache.get("myKey") 来愉快地使用LoadingCache了,它会主动加载数据,并在存储空间不够或者数据过期时清理掉不需要的数据,非常省心且方便。

这里有些重点参数,下面详细介绍下:

参数

作用

注意事项

maximumSize

缓存的k-v最大数据,当总缓存的数据量达到或者快达到这个值时,就会淘汰它认为不太用的一份数据,近似LRU或者LFU策略

并不一定是达到这个值才开始淘汰旧数据,可能接近时就会开始淘汰

expireAfterAccess

数据被访问后多久就会过期,这个策略主要是为了淘汰长时间不被访问的数据

数据过期不是立即淘汰,而是有数据访问时才会触发

expireAfterWrite

数据写入后多久过期,这个策略是为了防止旧数据被缓存过久

同上

refreshAfterWrite

数据写入后多久刷新一次,这个类似于expireAfterWrite,但它会主动更新数据

同上

concurrencyLevel

数据的并发级别,LoadingCache为了实现线程安全,它里面采用了类似Java7中ConcurrentHashMap的实现,采用了分段加锁的方式,分段数影响了它的最大并发量

recordStats

开启Cache的状态统计(默认是开启的)

开启这个是会影响到性能的,如果要求极致性能的话关注下个

  我们来重点介绍下CacheLoader CacheStats和RemovalListener,因为这三者涉及到了数据的加载、使用和删除的完整生命周期,先来看下CacheLoader。

CacheLoader

  CacheLoader的作用就是为了在Cache中数据缺失时加载数据,其中最重要的方法就是load()方法,你可以在load() 方法中实现对应key加载数据的逻辑。在调用LoadingCache的get(key)方法时,如果key对应的value不存在,LoadingCache就会调起你在创建cache时传入的CacheLoader的load方法。

CacheStats

  使用CacheStats cacheStats = cache.stats(); 我就可以获取到cache的stats数据。从cacheStats中我们可以看到cache的命中率、命中数、异常率、加载时延……等数据,通过这些数据就可以直观地看出我们cache的一些性能指标,如果做出一些参数调整。 比如如果命中率过低,我们是不是可以调整大下maximumSize,或者调整下数据的过期策略?

RemovalListener

  RemovalListener会在LoadingCache中数据被清理时调起,其实就是个监听器模式,这样你可以通过Listener实现对数据淘汰事件的监听,比如在数据淘汰时打一行日志啥的。使用方法也很简单,在Java8 上你可以直接使用lambda表达式,或者也可以自己实现RemovalListener接口,并在构建Cache时注册进去即可。

代码语言:javascript复制
public enum RemovalCause {
  EXPLICIT {
    @Override
    boolean wasEvicted() {
      return false;
    }
  },
  REPLACED {
    @Override
    boolean wasEvicted() {
      return false;
    }
  },

  COLLECTED {
    @Override
    boolean wasEvicted() {
      return true;
    }
  },
  EXPIRED {
    @Override
    boolean wasEvicted() {
      return true;
    }
  },

  SIZE {
    @Override
    boolean wasEvicted() {
      return true;
    }
  };
  abstract boolean wasEvicted();
}

  在RemovalListener内,我们可以通过RemovalListener获取到被删除的数据的key和value,也可以知晓数据被删除的原因。可以看到有个RemovalCause枚举类,详细说明了几种数据被清除的原因,比如被用户主动删除(RemovalCause.EXPLICIT),被替换(RemovalCause.REPLACED),过期淘汰(RemovalCause.EXPIRED),被GC收集器删除(RemovalCause.COLLECTED),容量不够导致的删除(RemovalCause.SIZE)。

   关于LoadingCache的介绍就到这了。再说下谷歌的guava包,其实guava是一个很好用的Java开源开发包,里面除了cache之外,还有各种集合工具、并发工具,Cache只是其中很小的一部分,后续有机会我们在详细探索下guava。今天的文章就到这了,大家觉得有用请点赞,喜欢请关注。

0 人点赞