缓存,在咱们平常开发中是必不可少的一种解决性能问题的方法。简单的说,cache 就是为了提高系统性能而开辟的一块内存空间。缓存的作用就是将数据保存在内存中,当有其余线程或者客户端须要查询相同的数据资源时,直接从缓存的内存块中返回数据,这样不但能够提高系统的响应时间,同时也能够节省对这些数据的处理流程的资源消耗,总体上来讲,系统性能会有大大的提高。
Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。总体上来讲Guava cache 是本地缓存的不二之选,简单易用,性能好。
1 基本使用
package com.cache;
import com.cache.domain.Employee;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheBuilderSpec;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.Weigher;
import com.google.common.collect.Maps;
import org.apache.commons.collections4.map.HashedMap;
import org.junit.Test;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* 缓存加载程序测试
*
* @author zhaoshuai11
* @date 2022/11/19
*/
public class CacheLoaderTest {
private Boolean FROM_DB = false;
@Test
public void testBasic() throws ExecutionException, InterruptedException {
LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
.maximumSize(10)
.expireAfterAccess(3, TimeUnit.SECONDS)
.build(CacheLoader.from(this::findEmployeeByName)); // 数据获取方法
/**
* expireAfterAccess:
* cache.get("TOM");
* assertLoadFromDB();
*
* cache.get("TOM");
* assertLoadFromCache();
*/
/**
* expireAfterAccess:
* cache.get("TOM");
* assertLoadFromDB();
* TimeUnit.SECONDS.sleep(5);
* cache.get("TOM");
* assertLoadFromDB();
*/
cache.get("TOM"); // Returns the value associated with key in this cache, first loading that value if necessary
assertLoadFromDB();
cache.get("TOM");
assertLoadFromDB();
}
@Test
public void testEvictionByWeight() {
Weigher<String, Employee> weigher = (key, value) ->
value.getName().length() + value.getDept().length() + value.getEmpID().length();
LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
.maximumWeight(45)
.concurrencyLevel(1)
.weigher(weigher)
.build(CacheLoader.from(this::findEmployeeByName));
cache.getUnchecked("Gavin");
assertLoadFromDB();
cache.getUnchecked("Kevin");
assertLoadFromDB();
cache.getUnchecked("Allen");
assertLoadFromDB();
assertEquals(cache.size(), 3);
assertNotNull(cache.getIfPresent("Gavin"));
cache.getUnchecked("Jason");
assertNull(cache.getIfPresent("Kevin"));
}
/**
* 测试逐出策略
*/
@Test
public void testEviction() throws ExecutionException {
/**
* public static <K, V> CacheLoader<K, V> from(Function<K, V> function) {
* return new FunctionToCacheLoader<>(function);
* }
*/
LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
.maximumSize(3)
.build(CacheLoader.from(this::findEmployeeByName));
cache.getUnchecked("ONE");
assertLoadFromDB();
cache.getUnchecked("TWO");
assertLoadFromDB();
cache.getUnchecked("THREE");
assertEquals(cache.size(), 3L);
cache.getUnchecked("FOUR");
/**
* 返回与此缓存中的键关联的值,如果没有键的缓存值,则返回null
*/
assertNull(cache.getIfPresent("ONE"));
assertNotNull(cache.getIfPresent("FOUR"));
}
/**
* TTL
* Access time = Write/Update/Read
*
* @throws InterruptedException 中断异常
*/
@Test
public void testEvictionByAccessTime() throws InterruptedException {
LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
.expireAfterAccess(2, TimeUnit.SECONDS)
.build(CacheLoader.from(this::findEmployeeByName));
assertNotNull(cache.getUnchecked("Alex"));
assertEquals(cache.size(), 1L);
TimeUnit.SECONDS.sleep(3);
assertNull(cache.getIfPresent("Alex"));
assertNotNull(cache.getUnchecked("Guava"));
TimeUnit.SECONDS.sleep(1);
assertNotNull(cache.getIfPresent("Guava"));
TimeUnit.SECONDS.sleep(1);
assertNotNull(cache.getIfPresent("Guava"));
// read 重置时间
TimeUnit.SECONDS.sleep(1);
assertNotNull(cache.getIfPresent("Guava"));
}
/**
* write time = write/update
*
* @throws InterruptedException 中断异常
*/
@Test
public void testEvictionByWriteTime() throws InterruptedException {
LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(CacheLoader.from(this::findEmployeeByName));
assertNotNull(cache.getUnchecked("Alex"));
assertEquals(cache.size(), 1L);
assertNotNull(cache.getUnchecked("Guava"));
TimeUnit.SECONDS.sleep(1);
assertNotNull(cache.getIfPresent("Guava"));
TimeUnit.MICROSECONDS.sleep(990);
assertNotNull(cache.getIfPresent("Guava"));
TimeUnit.SECONDS.sleep(1);
assertNull(cache.getIfPresent("Guava"));
}
@Test
public void testWeakKey() throws InterruptedException {
LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.weakValues()
.weakKeys()
.build(CacheLoader.from(this::findEmployeeByName));
assertNotNull(cache.getUnchecked("Alex"));
assertNotNull(cache.getUnchecked("Guava"));
System.gc();
TimeUnit.SECONDS.sleep(1);
assertNull(cache.getIfPresent("Alex"));
assertNull(cache.getIfPresent("Guava"));
}
@Test
public void testSoftKey() throws InterruptedException {
LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.softValues()
.build(CacheLoader.from(this::findEmployeeByName));
int i = 0;
while (true) {
cache.put("Alex " + i, Employee.builder().name("Alex" + i).dept("Alex " + i).empID("Alex " + i).build());
System.out.println("[Employee]-" + i++);
TimeUnit.MICROSECONDS.sleep(600);
}
}
@Test
public void testLoadNullValue() {
LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(CacheLoader.from(k -> "null".equals(k) ? null : findEmployeeByName(k)));
Employee alex = cache.getUnchecked("Alex");
assertNotNull(alex);
assertEquals(alex.getName(), "Alex");
/**
* CacheLoader returned null for key null
* 不允许拿到的值为 NULL
*/
try {
assertNull(cache.getUnchecked("null"));
fail("should not process to here.");
} catch (Exception e) {
assertTrue(e instanceof CacheLoader.InvalidCacheLoadException);
}
}
@Test
public void testLoadNullValueWithOptional() {
LoadingCache<String, Optional<Employee>> cache = CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.SECONDS)
.build(CacheLoader.from(e -> "null".equals(e) ?
Optional.fromNullable(null) : Optional.fromNullable(findEmployeeByName(e))));
Optional<Employee> alexOptional = cache.getUnchecked("Alex");
System.out.println(alexOptional); // Optional.of(Employee(name=Alex, dept=Alex, empID=Alex))
assertNotNull(alexOptional.get());
Optional<Employee> aNull = cache.getUnchecked("null");
System.out.println(aNull); // Optional.absent()
assertNull(aNull.orNull());
Employee employee = cache.getUnchecked("null")
.or(new Employee("default", "default", "default"));
assertNotNull(employee.getName());
}
/**
* update:
* Thread1=>Cache Service1 method1 : Alex X
* Thread2=>Cache Service2 method2 : Alex y
*/
@Test
public void testCacheRefresh_1() throws InterruptedException {
AtomicInteger count = new AtomicInteger();
LoadingCache<String, Long> cache = CacheBuilder.newBuilder()
.refreshAfterWrite(2, TimeUnit.SECONDS)
.build(CacheLoader.from(k -> {
count.getAndIncrement();
return System.currentTimeMillis();
}));
Long res_1 = cache.getUnchecked("Alex");
TimeUnit.SECONDS.sleep(3);
Long res_2 = cache.getUnchecked("Alex");
assertNotEquals(res_1, res_2);
assertEquals(count.get(), 2);
}
@Test
public void testCacheRefresh_2() throws InterruptedException {
AtomicInteger count = new AtomicInteger();
LoadingCache<String, Long> cache = CacheBuilder.newBuilder()
.build(CacheLoader.from(k -> {
count.getAndIncrement();
return System.currentTimeMillis();
}));
Long res_1 = cache.getUnchecked("Alex");
TimeUnit.SECONDS.sleep(3);
Long res_2 = cache.getUnchecked("Alex");
assertEquals(res_1, res_2);
assertEquals(count.get(), 1);
}
@Test
public void testCachePreLoad() {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.build(CacheLoader.from(String::toUpperCase));
Map<String, String> map = Maps.newHashMap(new HashedMap<String, String>() {
{
put("name", "NAME");
put("age", "age");
put("gender", "GENDER");
}
});
cache.putAll(map);
assertEquals(cache.size(), 3L);
assertEquals(cache.getUnchecked("name"), "NAME");
/**
* 数据与规则造假
*/
assertEquals(cache.getUnchecked("age"), "age");
}
@Test
public void testCacheRemoveNotification() {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(3)
.removalListener(notification -> {
if (notification.wasEvicted()) {
RemovalCause cause = notification.getCause();
/**
* 删除缓存项的原因
*/
assertEquals(cause, RemovalCause.SIZE);
assertEquals(notification.getKey(), "Alex");
}
})
.build(CacheLoader.from(String::toUpperCase));
cache.getUnchecked("Alex");
cache.getUnchecked("Tom");
cache.getUnchecked("Jack");
cache.getUnchecked("Jenny");
}
/**
* Guava Cache提供了一种很是简便的方式,用于收集缓存执行的统计信息,须要注意的是:
* 跟踪缓存操做将会带来性能的损失,想要收集缓存的信息,咱们只须要在使用CacheBuilder的时候声明咱们想要收集统计信息便可
* LoadingCache<String, String> cache = CacheBuilder.newBuilder()
* .recordStats()
* .build(CacheLoader.from(String::toUpperCase));
* <p>
* 想要获取统计的信息,咱们只须要经过Cache或LoadingCache调用stats()方法,就将返回一个CacheStats实例,
* 经过CacheStats实例能够获取到须要的统计信息:
* CacheStats stats = cache.stats();
* <p>
* 下面是一个概述的清单,咱们能够经过CacheStats获取的一些信息:
* 1、加载缓存条目值所耗费的平均时间;
* 2、请求的缓存条目的命中率;
* 3、请求的缓存条目的未命中率;
* 4、缓存条数被移除的数量;
* <p>
* requestCount():返回Cache的lookup方法查找缓存的次数,不论查找的值是否被缓存。
* <p>
* hitCount():返回Cache的lookup方法命中缓存的次数。
* <p>
* hitRate():返回缓存请求的命中率,命中次数除以请求次数。
* <p>
* missCount():返回缓存请求的未命中的次数。
* <p>
* missRate():返回缓存请求未命中的比率,未命中次数除以请求次数。
* <p>
* loadCount():返回缓存调用load方法加载新值的次数。
* <p>
* loadSuccessCount():返回缓存加载新值的成功次数。
* <p>
* loadExceptionCount():返回缓存加载新值出现异常的次数。
* <p>
* loadExceptionRate():返回缓存加载新值出现异常的比率。
* <p>
* totalLoadTime():返回缓存加载新值所耗费的总时间。
* <p>
* averageLoadPenalty():缓存加载新值的耗费的平均时间,加载的次数除以加载的总时间。
* <p>
* evictionCount():返回缓存中条目被移除的次数。
* <p>
* minus(CacheStats other):返回一个新的表示当前CacheStats与传入CacheStats之间差别的CacheStats实例。
* <p>
* plus(CacheStats other):返回一个新的表示当前CacheStats与传入CacheStats之间总计的CacheStats实例。
*/
@Test
public void testCacheStat() {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.recordStats()
.build(CacheLoader.from(String::toUpperCase));
/**
* 第一次,从DB中获取
*/
assertEquals(cache.getUnchecked("alex"), "ALEX");
CacheStats stats_1 = cache.stats();
assertEquals(stats_1.hitCount(), 0L);
assertEquals(stats_1.missCount(), 1L);
assertEquals(cache.getUnchecked("alex"), "ALEX");
CacheStats stats_2 = cache.stats();
assertEquals(stats_2.hitCount(), 1L);
assertEquals(stats_2.missCount(), 1L);
System.out.println(stats_2.missRate());
System.out.println(stats_2.hitRate());
}
/**
* 配置文件里读取
*/
@Test
public void testCacheSpec() {
String spec = "maximumSize=3";
LoadingCache<String, String> cache = CacheBuilder.from(CacheBuilderSpec.parse(spec))
.build(CacheLoader.from(String::toUpperCase));
}
private Employee findEmployeeByName(String name) {
FROM_DB = true;
return new Employee(name, name, name);
}
private void assertLoadFromDB() {
assertEquals(true, FROM_DB);
this.FROM_DB = false;
}
private void assertLoadFromCache() {
assertEquals(false, FROM_DB);
}
public static void main(String[] args) {
CacheLoaderTest cacheLoaderTest = new CacheLoaderTest();
Employee employeeByName = cacheLoaderTest.findEmployeeByName(null);
System.out.println(employeeByName);
}
}
2 测试
package com.cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.apache.commons.collections4.map.HashedMap;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class TestFinal {
private static final Map<String, Map<String, String>> MAP = new HashMap<String, Map<String, String>>() {
{
put("1001", new HashedMap<String, String>(){{put("name", "zhaoshuai11");}});
put("1002", new HashedMap<String, String>(){{put("age", "26");}});
put("1003", new HashedMap<String, String>(){{put("gender", "male");}});
}};
private final LoadingCache<String, Map<String, String>> cache =
CacheBuilder.newBuilder()
.maximumSize(10)
.build(CacheLoader.from(this::getInfo));
@Test
public void testData() {
getInfoFromCache(); // get info from db.
getInfoFromCache(); // null
getInfoFromCache(); // null
}
private Map<String, String> getInfoFromCache() {
Map<String, String> unchecked = cache.getUnchecked("1001");
assertNotNull(unchecked);
return unchecked;
}
private Map<String, String> getInfo(String id) {
System.out.println("get info from db.");
return MAP.get(id);
}
}