Guava Cache简介

缓存类型

  1. JVM 缓存
    首先是 JVM 缓存,也可以认为是堆缓存。其实就是创建一些全局变量,如 Map、List 之类的容器用于存放数据。这样的优势是使用简单但是也有以下问题:
    • 只能显式的写入,清除数据。
    • 不能按照一定的规则淘汰数据,如 LRU,LFU,FIFO 等。
    • 清除数据时的回调通知。
    • 其他一些定制功能等。
  2. Ehcache、Guava Cache
    所以出现了一些专门用作 JVM 缓存的开源工具出现了,如本文提到的 Guava Cache。
    它具有上文 JVM 缓存不具有的功能,如自动清除数据、多种清除算法、清除回调等。
    但也正因为有了这些功能,这样的缓存必然会多出许多东西需要额外维护,自然也就增加了系统的消耗。
  3. 分布式缓存
    刚才提到的两种缓存其实都是堆内缓存,只能在单个节点中使用,这样在分布式场景下就招架不住了。
    于是也有了一些缓存中间件,如 Redis、Memcached,在分布式环境下可以共享内存。具体不在本次的讨论范围。
    final static Cache cache = CacheBuilder.newBuilder()
            //设置cache的初始大小为10,要合理设置该值
            .initialCapacity(10)
            //设置并发数为5,即同一时间最多只能有5个线程往cache执行写入操作
            .concurrencyLevel(5)
            //设置cache中的数据在写入之后的存活时间为10秒
            .expireAfterWrite(10, TimeUnit.SECONDS)
            //构建cache实例
            .build();

清除缓存的策略

任何Cache的容量都是有限的,而缓存清除策略就是决定数据在什么时候应该被清理掉。GuavaCache提了以下几种清除策略:

  1. 基于存活时间的清除(Timed Eviction)
    这应该是最常用的清除策略,在构建Cache实例的时候,CacheBuilder提供两种基于存活时间的构建方法:
  • expireAfterAccess(long, TimeUnit):缓存项在创建后,在给定时间内没有被读/写访问,则清除。
  • expireAfterWrite(long, TimeUnit):缓存项在创建后,在给定时间内没有被写访问(创建或覆盖),则清除。expireAfterWrite()方法有些类似于redis中的expire命令,但显然它只能设置所有缓存都具有相同的存活时间。若遇到一些缓存数据的存活时间为1分钟,一些为5分钟,那只能构建两个Cache实例了。
  1. 基于容量的清除(size-based eviction)
  • 在构建Cache实例的时候,通过CacheBuilder.maximumSize(long)方法可以设置Cache的最大容量数,当缓存数量达到或接近该最大值时,Cache将清除掉那些最近最少使用的缓存。
  • 以上是这种方式是以缓存的“数量”作为容量的计算方式,还有另外一种基于“权重”的计算方式。比如每一项缓存所占据的内存空间大小都不一样,可以看作它们有不同的“权重”(weights)。你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。
  1. 显式清除
    任何时候,你都可以显式地清除缓存项,而不是等到它被回收,Cache接口提供了如下API:
  • 个别清除:Cache.invalidate(key)
  • 批量清除:Cache.invalidateAll(keys)
  • 清除所有缓存项:Cache.invalidateAll()

基于引用的清除(Reference-based Eviction)

在构建Cache实例过程中,通过设置使用弱引用的键、或弱引用的值、或软引用的值,从而使JVM在GC时顺带实现缓存的清除,不过一般不轻易使用这个特性。

  • CacheBuilder.weakKeys():使用弱引用存储键
  • CacheBuilder.weakValues():使用弱引用存储值
  • CacheBuilder.softValues():使用软引用存储值

清除什么时候发生?

也许这个问题有点奇怪,如果设置的存活时间为一分钟,难道不是一分钟后这个key就会立即清除掉吗?我们来分析一下如果要实现这个功能,那Cache中就必须存在线程来进行周期性地检查、清除等工作,很多cache如redis、ehcache都是这样实现的。
但在GuavaCache中,并不存在任何线程!它实现机制是在写操作时顺带做少量的维护工作(如清除),偶尔在读操作时做(如果写操作实在太少的话),也就是说在使用的是调用线程。
这在GuavaCache被称为“延迟删除”,即删除总是发生得比较“晚”,这也是GuavaCache不同于其他Cache的地方!这种实现方式的问题:缓存会可能会存活比较长的时间,一直占用着内存。如果使用了复杂的清除策略如基于容量的清除,还可能会占用着线程而导致响应时间变长。但优点也是显而易见的,没有启动线程,不管是实现,还是使用起来都让人觉得简单(轻量)。
如果你还是希望尽可能的降低延迟,可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()ScheduledExecutorService可以帮助你很好地实现这样的定时调度。不过这种方式依然没办法百分百的确定一定是自己的维护线程“命中”了维护的工作。

你可能感兴趣的:(Guava Cache简介)