赞
踩
Caffeine是基于JDK1.8版本的高性能本地缓存库,它是Guava的增强版,与ConcurrentLinkedHashMap相似,支持并发,并且可以在O(1)的时间复杂度内查找、写入元素。
private static void manual() { // 构建caffeine的缓存对象,并指定在写入后的10分钟内有效,且最大允许写入的条目数为10000 Cache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10_000) .build(); String key = "hello"; // 查找某个缓存元素,若找不到则返回null String str = cache.getIfPresent(key); System.out.println("cache.getIfPresent(key) ---> " + str); // 查找某个缓存元素,若找不到则调用函数生成,如无法生成则返回null str = cache.get(key, k -> create(key)); System.out.println("cache.get(key, k -> create(key)) ---> " + str); // 添加或者更新一个缓存元素 cache.put(key, str); System.out.println("cache.put(key, str) ---> " + cache.getIfPresent(key)); // 移除一个缓存元素 cache.invalidate(key); System.out.println("cache.invalidate(key) ---> " + cache.getIfPresent(key)); } private static String create(Object key) { return key + " world"; }
输出结果:
cache.getIfPresent(key) ---> null
cache.get(key, k -> create(key)) ---> hello world
cache.put(key, str) ---> hello world
cache.invalidate(key) ---> null
LoadingCache是附加在CacheLoader之上构建的缓存对象。
可以使用getAll方法执行批量查找,默认情况下,getAll()方法会单独调用CacheLoader.load()方法来加载每个不在缓存中的Key,必要情况下可以重写CacheLoader.loadAll()方法来弥补其缺陷。
public static void loading() { LoadingCache<String, String> cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(key -> create(key)); // 当调用get或者getAll时,若找不到缓存元素,则会统一调用create(key)生成 String key = "hello"; String str = cache.get(key); System.out.println("cache.get(key) ---> " + str); List<String> keys = Lists.newArrayList("a", "b", "c", "d", "e"); // 批量查找缓存元素,如果缓存不存在则生成缓存元素 Map<String, String> maps = cache.getAll(keys); System.out.println("cache.getAll(keys) ---> " + maps); } private static String create(Object key) { return key + " world"; }
输出结果
cache.get(key) ---> hello world
cache.getAll(keys) ---> {a=a world, b=b world, c=c world, d=d world, e=e world}
AsyncCache就是Cache的异步实现方式,提供了通过Executor生成缓存元素并返回CompletableFuture的能力。
synchronous()提供了在缓存计算完成前的阻塞能力,AsyncCache默认使用ForkJoinPool.commonPool()线程池,你也可以通过重写Caffeine.executor(executor)来实现自己的线程池。
private static void asynchronous() {
AsyncCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.buildAsync();
String key = "Hello";
// 查找某个缓存元素,若找不到则返回null
CompletableFuture<String> value = cache.getIfPresent(key);
// 查找某个缓存元素,若不存在则异步调用create方法生成
value = cache.get(key, k -> create(key));
// 添加或者更新一个缓存元素
cache.put(key, value);
// 移除一个缓存元素
cache.synchronous().invalidate(key);
}
AsyncLoadingCache就是LoadingCache的异步形式
private static void asynchronouslyLoading() {
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(10, TimeUnit.MINUTES)
// 异步构建一个同步的调用方法create(key)
.buildAsync(key -> create(key));
// 也可以使用下面的方式来异步构建缓存,并返回一个future
// .buildAsync((key, executor) -> createAsync(key, executor));
String key = "Hello";
// 查找某个缓存元素,若找不到则会异步生成。
CompletableFuture<String> value = cache.get(key);
List<String> keys = Lists.newArrayList("a", "b", "c", "d", "e");
// 批量查找某些缓存元素,若找不到则会异步生成。
CompletableFuture<Map<String, String>> graphs = cache.getAll(keys);
}
private static void sizeBased() {
// 基于缓存内元素的个数,尝试回收最近或者未经常使用的元素
LoadingCache<String, String> values = Caffeine.newBuilder()
.maximumSize(10_000)
.build(key -> create(key));
// 也可以基于缓存元素的权重,进行驱除
LoadingCache<String, String> graphs = Caffeine.newBuilder()
.maximumWeight(10_000)
.weigher((String key, String value) -> value.length())
.build(key -> create(key));
}
private static void timeBased() { // 自上一次写入或者读取缓存开始,在经过指定时间之后过期。 LoadingCache<String, String> fixedAccess = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .build(key -> create(key)); // 自缓存生成后,经过指定时间或者一次替换值之后过期。 LoadingCache<String, String> fixedWrite = Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .build(key -> create(key)); // 自定义缓存过期策略,可以在创建时,写入后、读取时。 LoadingCache<String, String> varying = Caffeine.newBuilder() .expireAfter(new Expiry<String, String>() { public long expireAfterCreate(String key, String value, long currentTime) { return currentTime; } public long expireAfterUpdate(String key, String value, long currentTime, long currentDuration) { return currentDuration; } public long expireAfterRead(String key, String value, long currentTime, long currentDuration) { return currentDuration; } }) .build(key -> create(key)); }
private static void referenceBased() {
// 当缓存key和value都不存在强引用关系时,进行驱逐
LoadingCache<String, String> weak = Caffeine.newBuilder()
.weakKeys()
.weakValues()
.build(key -> create(key));
// 当发生GC时进行驱逐
LoadingCache<String, String> soft = Caffeine.newBuilder()
.softValues()
.build(key -> create(key));
}
// 直接删除
cache.invalidate(key)
// 批量删除
cache.invalidateAll(keys)
// 删除所有
cache.invalidateAll()
监听缓存元素被删除或者被驱除事件
private static void removalsListeners() { Cache<String, String> cache = Caffeine.newBuilder() // 注意:evictionListener是3.X版本中新特性,2.X版本中没有 .evictionListener((String key, String value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .removalListener((String key, String value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(); cache.put("Hello", "Caffeine"); System.out.println(cache.getIfPresent("Hello")); cache.invalidate("Hello"); try { // 监听是异步执行的 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }
refresh只有在LoadingCache或者AsyncLoadingCache时才能使用,与驱逐不同之处,在刷新的时候,如果访问元素仍然可以返回,但返回的是旧值。
static StringBuffer stringBuffer = new StringBuffer(">"); private static void refresh() { LoadingCache<String, String> cache = Caffeine.newBuilder() .maximumSize(10_000) .refreshAfterWrite(1, TimeUnit.SECONDS) .build(key -> stringBuffer.append(key).toString()); for (int i = 0; i < 5; i++) { // 这里需要注意,前两次输出都是:“>*” // 理论上第二次输出应是:“>**”,这是因为refreshAfterWrite刷新实际上指的是在x秒、并且是第二次访问之后才开始刷新 System.out.println(cache.get("*")); try { Thread.sleep(1200); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果
>*
>*
>**
>***
>****
caffeine提供了完善的缓存统计能力,提供了metrics-caffeine类库,可以直接入Prometheus
private static void statistics() { Cache<String, String> cache = Caffeine.newBuilder() .maximumSize(10_000) .recordStats() .build(); cache.put("Hello", "Caffeine"); for (int i = 0; i < 15; i++) { // 命中15次 cache.getIfPresent("Hello"); } for (int i = 0; i < 5; i++) { // 未命中5次 cache.getIfPresent("a"); } cache.get("b", key -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return key + "B"; }); CacheStats stats = cache.stats(); System.out.println(stats); System.out.println("命中率:" + stats.hitRate()); System.out.println("未命中率:" + stats.missRate()); System.out.println("加载新值花费的平均时间:" + stats.averageLoadPenalty()); }
输出结果:
CacheStats{hitCount=15, missCount=6, loadSuccessCount=1, loadFailureCount=0, totalLoadTime=1014390300, evictionCount=0, evictionWeight=0}
命中率:0.7142857142857143
未命中率:0.2857142857142857
加载新值花费的平均时间:1.0143903E9
hitRate:命中率
hitCount:命中次数
missRate:未命中率
missCount:未命中次数
loadSuccessCount:成功加载新值的次数
loadFailureCount:失败加载新值的次数
totalLoadTime:总计加载的耗时
averageLoadPenalty:加载新值花费的平均时间
evictionCount:驱逐的条目数
evictionWeight:按权重驱除的次数
配置类
import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.RemovalCause; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class CacheConfig { @Bean public Cache<String, Object> caffeineCache() { return Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(1500, TimeUnit.MILLISECONDS) .removalListener((String key, Object value, RemovalCause cause) -> removalListener(key, value, cause)) .recordStats() .build(); } private void removalListener(String key, Object value, RemovalCause cause) { System.out.printf("Key: %s , Value: %s , was removed (%s)%n", key, value, cause); System.out.println(); } }
测试类
@RunWith(SpringRunner.class) @SpringBootTest public class CaffeineCacheTestTests { @Resource CacheConfig cacheConfig; @Test public void test() throws InterruptedException { Cache<String, Object> caffeineCache = cacheConfig.caffeineCache(); new Thread(new Runnable() { @SneakyThrows @Override public void run() { while(true){ Thread.sleep(1000); CacheStats cacheStats = caffeineCache.stats(); System.out.println("命中率:" + cacheStats.hitRate()); System.out.println("未命中率:" + cacheStats.missRate()); System.out.println(); } } }).start(); for (int i = 1; i <= 10; i++) { Object cacheKey = caffeineCache.getIfPresent("hello"); if (Objects.isNull(cacheKey)) { caffeineCache.put("hello", "caffeine"); } Thread.sleep(((i % 2) + 1) * 1000); } } }
测试结果
命中率:0.0 未命中率:1.0 Key: hello , Value: caffeine , was removed (EXPIRED) 命中率:0.0 未命中率:1.0 命中率:0.0 未命中率:1.0 命中率:0.3333333333333333 未命中率:0.6666666666666666 Key: hello , Value: caffeine , was removed (EXPIRED) 命中率:0.25 未命中率:0.75 命中率:0.4 未命中率:0.6 命中率:0.4 未命中率:0.6 Key: hello , Value: caffeine , was removed (EXPIRED) 命中率:0.3333333333333333 未命中率:0.6666666666666666 命中率:0.42857142857142855 未命中率:0.5714285714285714 命中率:0.42857142857142855 未命中率:0.5714285714285714 Key: hello , Value: caffeine , was removed (EXPIRED) 命中率:0.375 未命中率:0.625 命中率:0.4444444444444444 未命中率:0.5555555555555556 命中率:0.4444444444444444 未命中率:0.5555555555555556 Key: hello , Value: caffeine , was removed (EXPIRED) 命中率:0.4 未命中率:0.6
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。