5

谷歌Guava LoadingCache介绍

 1 year ago
source link: https://zxs.io/article/1884
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

谷歌Guava LoadingCache介绍

在这里插入图片描述

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

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

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

复制
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>31.1-jre</version>
</dependency>

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

复制
    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时注册进去即可。

复制
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。今天的文章就到这了,大家觉得有用请点赞,喜欢请关注。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK