​ Guava Cache 是 Google 开源的一套开发工具集合,Guava Cache 是其中的一个专门用于处理本地缓存的轻量级框架,是全内存方式的本地缓存,而且是线程安全的。和 ConcurrentMap 相比,Guava Cache 可以限制内存的占用,并可设置缓存的过期时间,可以自动回收数据,而 ConcurrentMap 只能通过静态方式来控制缓存,移除数据元素需要显示的方式来移除。下面通过例子来演示Guava Cache 的使用方式:

​ 使用 Guava Cache 的Maven 以来如下,Guava Cache 是 Google Guava 工具库中的一部分,不能单独应用,所以要把整个 Guava 库都引入进来,例如:

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>23.0</version>
</dependency>

​ 在 Guava Cache 中需要通过CacheBuilder创建缓存对象,并通过 CacheLoader 和 Callable 两种方式定义原始数据的获取方式,CacheLoader 是在缓存对象声明时指定,Callable 是在获取数据时指定,相比于 CacheLoader ,Callable 更加灵活,当然也可以通过 cache.put(key,value) 的方式直接插入。

LoadingCache<Long, UserInfo> cache = CacheBuilder.newBuilder()
  // 设置缓存个数
  .maximumSize(1000).build(new CacheLoader<Long, UserInfo>() {
  @Override
  public UserInfo load(Long key) throws Exception {
    return getUserInfoByUserId(key);
  }
});

try {
  // 读取缓存
  UserInfo user = cache.get(100001L);
} catch (ExecutionException e) {
  e.printStackTrace();
  doSomethingForException();
}

下面是通过 CacheLoader 使用缓存的例子:

// 定义缓存
Cache<Object, Object> cache = CacheBuilder.newBuilder().maximumSize(1000).build();

try {
  // 用户id
  final Long userId = 100001L;
  UserInfo user = (UserInfo) cache.get(userId, new Callable<UserInfo>() {
    @Override
    public UserInfo call() throws Exception {
      return getUserInfoByUserId(userId);
    }
  });
} catch (ExecutionException e) {
  doSomethingForException();
}

​ 如果 key 为自定义对象,则需要重写 boolean equals(Object obj) 方法,Guava Cache 默认情况(使用弱引用保存key的情况除外,这种情况按照内存地址进行比较)下按照 equals 方法比较key是否相同。

缓存回收:

​ 内存空间不是无限大的,当没有足够的内存情况下,必须要考虑缓存数据回收的问题,移除旧缓存数据为新的缓存数据腾出空间。Guava Cache 支持四种回收方式,分别是基于容量回收(Size-based Eviction),基于时间回收(Timed Eviction)和基于引用类型的回收(Reference-based Eviction)和手动回收。

  • 基于容量回收(Size-based Eviction)

    这种方式可以将缓存数据的数目限制在一个最大值一下,再缓存数据数据接近这个最大值时,Guava Cache 会按照LRU原则回收最近没有使用或者很少使用缓存数据,再开吗中可以通过 CacheBuilder.maximumSize(long) 方法进行设置,但在使用前,你最好预估一下的数据所占用的内存空间,达到最大值时是否有可能造成内存溢出。

  • 基于时间回收(Timed Eviction),Guava Cache 定义了两种基于时间的缓存回收方式:

    • CacheBuilder.expireAfterAccess(duration,unit):缓存项在给定时间内没有给访问过(包括读写操作)则会被回收,回收方式按照最近最少使用的原则。代码例如:CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES),表示缓存项在10分钟之内没有读写过,则会被回收。这种方式在实际项目场景中很有用,它可以确保在缓存条目有限的情况下,缓存中保留的都是最近经常使用的热点数据,从而提高缓存的整体命中率。
    • CacheBuilder.expireAfterWrite (duration, unit),另外一种方式是在缓存项在给定时间内没有被更新(包括创建和覆盖),则可以回收。
  • 基于引用类型的回收(Reference-based Eviction):

    Java语言中有强引用,弱引用和软引用的概念,强引用就是最常见的应用方式,被变量赋值就是强引用,只要引用关系存在,应用的对象就不会被垃圾回收。相比于强引用,使用弱引用关联的对象,只要系统进行垃圾回收,弱应用对象就会被回收,而软应用的强度介于弱引用和强引用之间,在内存空间不足,系统内存溢出之前,使用软引用关联的对象才会被回收。

    • CacheBuilder.weakKeys():按照弱引用的方式存储键值,当没有引用(强应用或者软引用)指向某键值时,允许系统在进行垃圾回收时回收缓存项。因为垃圾回收仅仅依赖于恒等式(==)来比较对象(对象地址比较),该方法使得缓存在比较键值时用恒等式(==)比较,而不是 equals() 方法。
    • CacheBuilder.weakValues():和 CacheBuilder.weakKeys() 方法类似,该方法按照弱引用方式来存储缓存项的值,允许系统垃圾回收时回收缓存项。
    • CacheBuilder.weakValues(),使用软引用方式来包装缓存值,只有在内存需要时(一般在接近内存溢出时),系统会按照全局LRU(least-recently-used)原则进行垃圾回收。考虑到垃圾回收的性能问题,推荐使用基于容量的回收方式。
  • 手动回收方式:

    除了上述三种自动回收方式以外,还可以通过如下方法进行手动缓存回收:

    • 回收单个缓存项,Cache.invalidate(key)
    • 批量回收,Cache.invalidateAll(keys)
    • 全部回收 Cache.invalidateAll()

​ 实际上,Guava Cache 并不会自动清理缓存,而是在写操作时捎带完成清理工作,如果写操作如果过少的花,也会在部分读操作中进行清理。Guava Cache 之所以不单独启一个线程来实现缓存的自动清理,是为了降低维护成本,避免和用户线程之间产生竞争,而且某些场景下,可能并不能随意创建线程。如果你不想让清理工作影响你的缓存的读写操作,你可以自行维护一个清理线程,周期性调用 cache.cleanUp() 方法定期清理。

​ 另外,Guava Cache 还提供了一些运维方法,帮助我们监控缓存运行状使用 CacheBuilder.recordStats()开启统计功能,cache.stats() 返回的 CacheStats 对象来查看运行的统计信息,可以监控好的信息有,主要的统计功能包括:

  • hitRate(), 缓存命中率。
  • hitCount(),缓存命中次数。
  • loadCount(),新值加载次数。
  • requestCount(),缓存访问次数,是命中次数和非命中次数之和。
  • averageLoadPenalty(),加载新值时的平均耗时,以纳秒为单位。
  • evictionCount(), 除了手动清除意外的缓存回收总次数。

除此之外,还有其他很多统计信息,这些信息可以有效帮助我们优化缓存的设置。

并发级别设置:

​ 类似于 ConcurrentHashMap 中的并发级别,Guava Cache 通过改值讲内部数据结构拆分成固定数量的段,缓存数据在段中存储,每个段有自己的写锁,段与段之间互不影响,所以并发级别越大,分的段越多,并发能力越强,如果你的服务QPS比较大的花,可以适当调大并发级别,用法如:

CacheBuilder.newBuilder().concurrencyLevel(20)

results matching ""

    No results matching ""