Guava Cache

内容摘要

  • 写入数据到缓存
    • 手动写入 (put)
    • 自动加载(按需加载)
  • 数据清理
    • 过期、清理
      • 基于容量的清理触发条件
      • 基于时间的过期方案
      • 基于Reference Key,Value
      • 手动移除 (显式移除)
    • RemoveListener
    • 数据清理时机
  • refresh
  • 配置说明
  • 场景说明

 

 

1、写数据到缓存

 

1.1 手动写入 put

Guava Cache 底层是由一个ConcurrentMap实现的,那么底层必然支持两个常规的put:put(k,v), putIfAbsent(k,v)。

从类图上来看,基于Guava Cache实现的cache必然是有一个put(k,v)方法的,直接放k,v对到cache中,并替换掉原有的数据。从接口Cache 来看,只提供了一个put(k,v)方法,底层是一个ConcurrentMap,那么putIfAbsent(k,v)的特性好像被丢弃了。如果你希望使用putIfAbsent,那么可以调用Cache.asMap().putIfAbsent(k,v)。

 

 

1.2 自动加载(按需加载)

 

除了上面的put直接写入cache方式外,Guava Cache还提供了两种实现了get-if-absent-compute语义的方式。

所谓的get-if-absent-compute语义是说:在调用get方法时,如果发现指定的值不存在,则通过加载、计算等方式来提供值。也可以将这个语义理解为lazy load(懒加载、按需加载)。

 

这两种方式分别为:Cache.get(key, Callable) 、LoadingCache。

 

Cache.get(key, Callable) 在调用get时,指定一个Callable,如果值不存在时,调用Callable来计算值。计算到值后放入Cache中,并返回结果。

 

LoadingCache 必须有一个CacheLoader配合一起使用,LoadingCache与CacheLoader的几个方法的调用关系:

LoadingCache.get(k) ->  CacheLoader.load(k)

LoadingCache.refresh(k) ->   CacheLoader.reload(k)

LoadingCache.getAll(keys) -> CacheLoader.loadAll(keys)

CacheLoader是不保证一定可以加载成功,所以它的所有方法都是有异常的。

 

2、数据清理

任何一种Cache都必须支持一定的Cache清理策略,不然数据会一直增长,内存肯定是扛不住的。Guava Cache也提供了多种Cache清理策略:

2.1 过期、清理

Guava Cache采用基于容量、Soft引用、Weak引用的清理触发条件,基于过期时间、权重来决定哪些数据优先清理,采用LRU算法来清理数据。

 

2.1.1 基于容量的清理触发条件

如果你的cache数量不应该一直保持增长状态,你需要设定总量来限定cache的容量。可以通过CacheBuilder.maximumSize(long capacity)来限定。当容量即将达到上限时,会自动的进行数据清理。默认的最大容量是 1<<30,如果想要限定,只能设置比1<<30小的数。

       因为Guava Cache有默认容量,也就是最大容量的限制,所以任何一个Guava Cache都是有界cache,不会无限制的增加。

 

       在快要达到容量限定值开始清理数据时,采用的是LRU清理算法。与此同时,还可以根据每一个key-value的weight来控制清理,一个key-value的weight越低越容易被清除掉。默认情况下,每一个key-value的weight都是一样的,即为1。当然了你可以自定义weight算法,通过CacheBuilder.weight(weight)即可。

 

其实严格来讲,这个并不属于

 

2.1.2 基于时间的过期方案

Guava Cache提供了两种基于时间的数据过期方案:

1)     expireAfterWrite()

2)     expireAfterAccess()

 

根据访问后的时间来控制数据是否过期。需要注意的是,设置了基于时间后,不会在内部另外启动线程来定时清理掉过期的数据的。是依赖的与get请求,也就是说每一次访问一个key时,会记录时间,再一次访问时,会先判断数据是否过期了,一旦过期了,就清理掉,被清理掉后,下一次访问时,会调用配置的CacheLoader进行加载。

 

当使用基于时间的过期策略时,可以自定义自己的计时器的,使用CacheBuilder.ticker(ticker)即可。

 

需要说明的是,这里的基于时间的过期策略是针对每一个key-value的,如果你的业务中极有可能每一个key-value都不一样的话,就不适合使用了。如果你对业务中有可以枚举的几个时间过期粒度,可以创建多个Guava Cache来完成的。

 

2.1.3 基于Reference的Key、Value

可以利用weak reference, soft reference来清理缓存。具体来说 weak reference 可以用在key, value上;soft reference 可以用在 value上。

 

 2.1.4 手动移除(显示移除)

当然了,Guava Cache也提供了最基本的手动移除key-value的方案,直接从cache移除:

Cache.invalidate(key) 单个移除

Cache.invalidateAll(keys) 批量移除

Cache.invalidate() 移除所有

 

 2.2 RemoveListener

如果你的业务对数据的清理感兴趣,还可以指定RemoveListener的。

 

 2.3 数据清理时机

从上面的几种清理策略或者过期策略来看,Guava Cache 提供的是被动清理方案,不论是基于容量、基于时间、基于Reference,它们都是被动的清理方案。而手动清理方案虽然是一种主动的清理方案,但它只是针对与你已经确定了不会再用的数据。

 

       Cache.cleanUp() 是一种主动的清理方案,它会清理掉过期的数据。

 

 

3、refresh

LoadingCache中的refresh 提供了值替换的功能。在调用refresh时,会先调加载新值,新值加载到后替换掉老值,并返回老值。如果加载不到新值,老值是被保留的,不会被替换掉的。

 

LoadingCache是引发CacheLoader调用reload方法的,CacheLoader的reload返回的是ListenableFuture,也就表明了,支持同步reload,也支持异步的reload的。

 

 

4、配置说明

Guava Cache中的两种cache(Cache,LoadingCache)的实现分别由LocalManuelCache、LocalLoadingCache来完成。创建这两种cache也很简单,使用CacheBuilder即可。在构建cache时,需要配置数据清理策略、并发级别等。

 

 

concurrencyLevel:底层的LocalCache 也是一个ConcurrentMap,它的实现与JDK8之前版本中的ConcurrentHashMap类似,也采用了多个segment来提高并发。他们都可以配置concurrencyLevel,来控制segment的数量,来提高并发。ConcurrentHashMap的默认值是16,最大值是1<<16;这里的默认值是4,最大值为 1<<16。

 

initialCapacity:初始容量配置,默认值是16.

maximumSize:最大容量。

maximumWeight:最大权重

 

keyEquivalence、valueEquivalence,用于指定内部判断key,value时,是否一样的算法。另外需要说明的是,默认情况下Guava Cache内部判断key, value是否相等时,针对不同的配置采用不同的方案的。如果启用了weakKey,weakValue,softValue的情况下,相应的key或者value的相等性判断是采用的是 ==,如果没有启用weakKey,softValue,weakValue的情况下,相应的key,value的相等性判断使用是equals()。

 

 

5、场景说明

不适用于数据有不同的过期时间的场景。

 

点赞