在分布式系统中,分布式缓存如redis,memcache使用比较多。实际上,本地缓存在很多场景下也有使用的必要。
本文主要介绍google的工具包guava中的缓存工具的使用。
本地缓存应用场景:
(1)对性能有非常高的要求
(2)不经常变化或者存在热词
(3)可以接受数据的非实时性
本地缓存可以通过几种方式来实现:ConcurrentHashMap,Guava cache或Ehcached。这里说说Guava cache的使用。
其特性如下:
- 使用LRU缓存过期机制
- 并发处理能力:类似于ConcurrentHashMap,是线程安全的,采用了分段锁机制。
- 更新锁定:在CacheLoader的load方法中加以控制,对同一个key,只让一个请求去读源并设置到缓存中,其他请求阻塞等待(防止缓存击穿)。
- 集成数据源:get方法从缓存中读取不到时可以从数据源中读取数据并回填到缓存中。
- 监控缓存加载/命中情况
下面直接看代码:
public class LocalCache {
public static void main(String[] args) throws Exception {
LoadingCache<ReqArg, String> loadingCache = CacheBuilder.newBuilder()
.recordStats().maximumSize(1000).expireAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<ReqArg, String>() {
@Override
public String load(ReqArg key) throws Exception {
return key.getKey();
}
});
Thread checkupThread = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
System.out.println(loadingCache.stats());
} catch (InterruptedException e) {
break;
}
}
});
checkupThread.setDaemon(true);
checkupThread.start();
for (int i = 0; i < 20; i++) {
String key = String.valueOf(i);
ReqArg reqArg = new ReqArg(key, key);
System.out.println(loadingCache.get(reqArg));
}
for (int i = 0; i < 10; i++) {
String key = String.valueOf(i);
ReqArg reqArg = new ReqArg(key, key);
System.out.println(loadingCache.get(reqArg));
}
Thread.sleep(10 * 60 * 3600);
}
static class ReqArg {
String key;
String id;
public ReqArg() {
}
public ReqArg(String key, String id) {
this.key = key;
this.id = id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(key)
.append(id)
.toHashCode();
}
@Override
public boolean equals(Object obj) {
ReqArg args = (ReqArg) obj;
return new EqualsBuilder()
.append(this.key, args.key)
.append(this.id, args.id)
.isEquals();
}
// public boolean equals(Object o) {
// return EqualsBuilder.reflectionEquals(this, o);
//
// }
//
// public int hashCode() {
// return HashCodeBuilder.reflectionHashCode(this);
// }
}
}
运行上述代码,输出结果如下:
0
1
...
CacheStats{hitCount=10, missCount=20, loadSuccessCount=20, loadExceptionCount=0, totalLoadTime=2012339, evictionCount=0}
CacheStats{hitCount=10, missCount=20, loadSuccessCount=20, loadExceptionCount=0, totalLoadTime=2012339, evictionCount=0}
...
上面的数字部分是两个for循环输出的结果。下面的缓存命中统计情况是线程checkupThread输出的。其含义如下:
缓存命中10次,未命中20次,从数据源加载数据20次。
在第一个for循环中,此时缓存是空的,每次都需要从数据源读取数据(load方法),并加载到缓存中。20次全部未命中。
在第二个for循环中,所有的数据都已经被缓存了,所以直接从缓存中读取。10次全部命中缓存。