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技术,它在项目中被用来实现本地缓存。
这里提到的缓存技术通常指内存缓存,主要为了解决业务对于后台服务低延迟、高可用的要求。
缓存技术有大量的应用实例,比如:cpu 缓存,数据库缓存,网络缓存,分布式缓存等等。
在我们的项目中常有的应用场景是:对于一份数据获取或者处理时间较长,比如较多的磁盘I/O操作、较多网络I/O请求、较复杂的处理逻辑等等,
并且在一定时间内这份数据不会发生变化。
此时为了保证业务对于响应速度的要求,在后台服务中典型的解决方案是采用内存缓存技术。
当然在提高性能的同时也提高了系统的可用性,假设在生成数据过程中发生异常,在缓存存在的情况下,可以直接返回旧的数据,不至于返回错误
信息给业务。
另外生成数据的过程消耗的系统资源较多,在多人请求同一份数据时,系统就会浪费大量资源,甚至可能宕机。因为这份数据并不需要为
每个用户产生不同内容,其实只需要在系统维持一份缓存即可。
Guava cache就是一个实现缓存技术的简单易用的库,它包含以下特性:
支持中断
这些特性使得在实现缓存策略时开发成本非常低,可靠性非常高。
参照官网的wiki,他的应用场景如下:
利用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
从执行结果看,符合预期。
在没有默认方法来加载或计算与键关联的值,或者需要覆盖默认的加载运算,可以在调用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}"
})
}
下面这段总结参考: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)刷新出现异常的情况下不会影响取值和继续刷新