Guava cache介绍

Guava是谷歌开源的java工具类jar包,是Google的java核心库,许多java项目都依赖它。Guava包括:集合collections,
缓存caching,原生类型primitives support,并发库concurrency libraries,通用注解common annotations等等。

Guava cache 中文文档:http://ifeve.com/google-guava-cachesexplained/

本篇wiki介绍Guava cache技术,它在项目中被用来实现本地缓存。

1、缓存技术

这里提到的缓存技术通常指内存缓存,主要为了解决业务对于后台服务低延迟、高可用的要求。

缓存技术有大量的应用实例,比如:cpu 缓存,数据库缓存,网络缓存,分布式缓存等等。

在我们的项目中常有的应用场景是:对于一份数据获取或者处理时间较长,比如较多的磁盘I/O操作、较多网络I/O请求、较复杂的处理逻辑等等,

并且在一定时间内这份数据不会发生变化。

此时为了保证业务对于响应速度的要求,在后台服务中典型的解决方案是采用内存缓存技术。

当然在提高性能的同时也提高了系统的可用性,假设在生成数据过程中发生异常,在缓存存在的情况下,可以直接返回旧的数据,不至于返回错误

信息给业务。

另外生成数据的过程消耗的系统资源较多,在多人请求同一份数据时,系统就会浪费大量资源,甚至可能宕机。因为这份数据并不需要为

每个用户产生不同内容,其实只需要在系统维持一份缓存即可。

2、Guava cache

Guava cache就是一个实现缓存技术的简单易用的库,它包含以下特性:

  1. 全部内存实现
  2. 性能较好
  3. 限制内存使用
  4. 多线程安全
  5. 支持中断

这些特性使得在实现缓存策略时开发成本非常低,可靠性非常高。

参照官网的wiki,他的应用场景如下:

  1. 牺牲存储换取相应速度
  2. 系统中某些数据可能被用到多次
  3. 缓存的数据量较小

3、Guava cache之CacheLoader

利用CacheLoader方式可以几行代码实现一个Guava cache,实例:

val expiredTime = 10
val myCache = CacheBuilder.
  newBuilder().
  expireAfterWrite(expiredTime, TimeUnit.SECONDS).
  build(new CacheLoader[String, String]() {
      def load(key: String) = {
        LOG.info("未命中缓存")
        s"key is ${key}"
      }
})

expireAfterWrite用于设置失效时间,这里指在load这个key后会在10秒后失效,当10秒后再次获取这个key的value时会重新执行load函数。
CacheLoader[String, String]表示key类型是String,value类型也是String。当时key和value也可以是更丰富的类型,例如CacheLoader[String, Map[String,String]]。
load函数用来获取key的value,当cache中已经存在某个key对应的value失效时,Guava cache会调用这个函数来获取value。当value未失效时,直接返回value。

注意:load函数执行时属于原子操作,保证了线程安全。

获取某个key的value:

while(true) {
  println(myCache.get("key1"))
  println(myCache.get("key2"))
  Thread.sleep(5000)
}

可以看到value的获取方式非常简单自然,这里在每次获取之后会sleep 5秒。

对于执行结果的预期是:首次缓存未命中,五秒之后缓存命中,再五秒之后缓存未命中。

执行结果:

2015-05-30 22:55:42,719 INFO  (sgAgentSelfCheck.scala:192) - 未命中缓存
key is key1
2015-05-30 22:55:42,734 INFO  (sgAgentSelfCheck.scala:192) - 未命中缓存
key is key2
key is key1
key is key2
2015-05-30 22:55:52,751 INFO  (sgAgentSelfCheck.scala:192) - 未命中缓存
key is key1
2015-05-30 22:55:52,752 INFO  (sgAgentSelfCheck.scala:192) - 未命中缓存
key is key2

从执行结果看,符合预期。

4、Guava cache之Callable

在没有默认方法来加载或计算与键关联的值,或者需要覆盖默认的加载运算,可以在调用get时传入一个Callable实例。

Callable实例:

private val expiredTime = 10
private val myCache = CacheBuilder.newBuilder().expireAfterWrite(expiredTime, TimeUnit.MINUTES)
  .build[String, String]
def get(key: String) {
    myCache.get(key, new Callable[String] {
    override def call() = s"key is ${key}"
    })
}

5、其他

下面这段总结参考:http://www.cnblogs.com/peida/p/Guava_Cache.html

参数说明:
  1. 大小的设置:CacheBuilder.maximumSize(long)  CacheBuilder.weigher(Weigher)  CacheBuilder.maxumumWeigher(long)
  2. 时间:expireAfterAccess(long, TimeUnit) expireAfterWrite(long, TimeUnit)
  3. 引用:CacheBuilder.weakKeys() CacheBuilder.weakValues()  CacheBuilder.softValues()
  4. 明确的删除:invalidate(key)  invalidateAll(keys)  invalidateAll()
  5. 删除监听器:CacheBuilder.removalListener(RemovalListener)

refresh机制:
  1. LoadingCache.refresh(K)  在生成新的value的时候,旧的value依然会被使用。
  2. CacheLoader.reload(K, V) 生成新的value过程中允许使用旧的value
  3. CacheBuilder.refreshAfterWrite(long, TimeUnit) 自动刷新cache

实例:


var i = 0
  val scheduler = Executors.newScheduledThreadPool(2)
  val ses = Executors.newSingleThreadScheduledExecutor()
  val myCache = CacheBuilder
    .newBuilder()
    .build(
      new CacheLoader[String, String]() {
        override def load(key: String) = {
          LOG.info("come here")
          i.toString
        }
        override def reload(key: String, value: String) = {
          //异步
          val task = ListenableFutureTask.create(new Callable[String]() {
            def call() = {
              Thread.sleep(3000)
              i = i+1
              if(i == 3)
                throw new IllegalStateException("Exception thrown")
              i.toString
            }
          })
          LOG.info("start")
          scheduler.execute(task)
          LOG.info("end")
          task
        }
      }
    )
  println(new DateTime().toString("yyyy-MM-dd HH:mm:ss"))
  scheduler.scheduleAtFixedRate(new Runnable() {
    def run() {
      myCache.refresh("ddd")
    }
  }, 0, 2, TimeUnit.SECONDS)
  while (true) {
   Thread.sleep(3000)
   LOG.info(myCache.get("ddd"))
 }
}

上面的例子实现了,缓存自动刷新的功能,覆盖reload方法实现的异步刷新机制的好处是:

(1)异步刷新不影响取值效率

(2)刷新未完成情况下,依旧返回旧值

(3)刷新出现异常的情况下不会影响取值和继续刷新





你可能感兴趣的:(缓存)