# caffeine-demo **Repository Path**: linkrwx/caffeine-demo ## Basic Information - **Project Name**: caffeine-demo - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2021-06-17 - **Last Updated**: 2021-06-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README @[toc] ## Java高性能本地缓存框架Caffeine > 缓存又分进程内缓存和分布式缓存两种:分布式缓存如redis、memcached等,还有本地(进程内)缓存如ehcache、GuavaCache、Caffeine等 > Caffeine是一个基于Java8开发的提供了[近乎最佳](https://github.com/ben-manes/caffeine/wiki/Efficiency-zh-CN)命中率的[高性能](https://github.com/ben-manes/caffeine/wiki/Benchmarks-zh-CN)的缓存库。 > Caffeine 是一个基于Java 8的高性能本地缓存框架,其结构和 Guava Cache 基本一样,api也一样,基本上很容易就能替换。 Caffeine 实际上就是在 Guava Cache 的基础上,利用了一些 Java 8 的新特性,提高了某些场景下的性能效率。 文档参考:https://github.com/ben-manes/caffeine/wiki/Home-zh-CN ### 如何使用 在项目中添加Caffeine的依赖 ```xml 4.0.0 org.example caffeine-demo 1.0-SNAPSHOT org.apache.maven.plugins maven-compiler-plugin 8 8 com.github.ben-manes.caffeine caffeine 2.8.6 org.junit.jupiter junit-jupiter-engine 5.5.2 test ``` ### 缓存加载 Caffeine提供了四种缓存添加策略:手动加载,自动加载,手动异步加载和自动异步加载。 #### 手动加载 Caffeine 有两种方式限制缓存大小。**两种配置互斥,不能同时配置** 1. **创建一个限制容量 Cache** ```java Cache cache = Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.SECONDS) // 设置超时时间为5s / 写入后隔段时间过期 .maximumSize(1)// 设置缓存最大条目数,超过条目则触发回收。 .build(); ``` 需要注意的是,实际实现上为了性能考虑,这个限制并不会很死板: - 在缓存元素个数快要达到最大限制的时候,过期策略就开始执行了,所以在达到最大容量前也许某些不太可能再次访问的 Entry (Key-Value)就被过期掉了。 - 有时候因为过期 Entry 任务还没执行完,更多的 Entry 被放入缓存,导致缓存的 Entry 个数短暂超过了这个限制 示例: ```java /** * 手动加载cache */ @Test public void testManualLoadCache2() throws InterruptedException { Cache cache = Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.SECONDS) // 设置超时时间为5s / 写入后隔段时间过期 .maximumSize(1)// 设置缓存最大条目数,超过条目则触发回收。 .build(); // 查找一个缓存元素, 没有查找到的时候返回null String value = cache.getIfPresent("test"); System.out.println(value);//-->null // 查找缓存,如果缓存不存在则生成缓存元素, 如果无法生成则返回null value = cache.get("test", k -> "test-value"); System.out.println(cache.getIfPresent("test"));//-->test-value System.out.println(value);//-->test-value // 加入一些缓存数据 List list = new ArrayList<>(); for (int i = 2; i < 10; i++) { list.add("test" + i); } for (int i = 2; i < 10; i++) { // 添加或者更新一个缓存元素 cache.put("test" + i, "test-value" + i); } // 执行缓存回收 // 缓存的删除策略使用的是惰性删除和定时删除,但是我也可以自己调用cache.cleanUp()方法手动触发一次回收操作。cache.cleanUp()是一个同步方法。 cache.cleanUp(); //根据key list去获取一个map的缓存 Map dataObjectMap = cache.getAllPresent(list); //查看缓存中的数据 System.out.println(dataObjectMap.size()); //--> 1 System.out.println(dataObjectMap); //--> {test9=test-value9} Thread.sleep(5000); //设置10s的睡眠时间,使得超过过期时间 System.out.println(cache.getIfPresent("test"));//-->null } ``` 2. **创建一个自定义权重限制容量的 Cache** ```java Cache> stringListCache = Caffeine.newBuilder() //最大weight值,当所有entry的weight和快达到这个限制的时候会发生缓存过期,剔除一些缓存 .maximumWeight(1) //每个 Entry 的 weight 值 .weigher(new Weigher>() { @Override public @NonNegative int weigh(@NonNull String key, @NonNull List value) { return value.size(); } }) .build(); ``` 上面我们的 value 是一个 list,以 list 的大小作为 Entry 的大小。当把 Weigher 实现为只返回1,maximumWeight 其实和 maximumSize 是等效的。 同样的,为了性能考虑,这个限制也不会很死板。 示例: ```java @Test public void testManualLoadCache4() { Cache> stringListCache = Caffeine.newBuilder() //最大weight值,当所有entry的weight和快达到这个限制的时候会发生缓存过期,剔除一些缓存 .maximumWeight(1) //每个 Entry 的 weight 值 .weigher(new Weigher>() { @Override public @NonNegative int weigh(@NonNull String key, @NonNull List value) { return value.size(); } }) .build(); stringListCache.put("test1", Collections.singletonList("test-value1")); stringListCache.put("test2", Arrays.asList("test-value2","test-value2")); stringListCache.cleanUp(); Map> dataObjectMap = stringListCache.getAllPresent(Arrays.asList("test1","test2")); System.out.println(dataObjectMap.size()); // --> 1 System.out.println(dataObjectMap); // --> {test1=[test-value1]} } ``` 3. **指定初始大小** ```java Cache cache = Caffeine.newBuilder() //指定初始大小 .initialCapacity(1000) .build(); ``` 和`HashMap`类似,通过指定一个初始大小,减少扩容带来的性能损耗。这个值也不宜过大,浪费内存。 #### 自动加载 - **创建LoadingCache** 示例: ```java @DisplayName("测试LoadingCache") @Test public void testLoadingCache() { LoadingCache cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(new CacheLoader() { @Override public @Nullable String load(@NonNull String key) throws Exception { //默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法进行加载 System.out.println("load data --- " + key); //模拟从数据库中获取数据 return MAP.get(key); } }); System.out.println(cache.get("test1")); //第一次的时候会调用load方法 System.out.println(cache.get("test1")); //第二次不会调用load方法 } ``` #### 手动异步加载 - **创建AsyncCache** 示例: ```java @Test public void testAsyncCache() throws ExecutionException, InterruptedException { AsyncCache cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10_000) .buildAsync(); // 查找缓存元素,如果不存在,则异步生成 CompletableFuture value = cache.get("test1", k -> { //异步加载 System.out.println(Thread.currentThread().getName()); // ForkJoinPool.commonPool-worker-3 System.out.println("load cache ---" + k); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return MAP.get(k); }); System.out.println(Thread.currentThread().getName()); //main System.out.println("========="); System.out.println(value.get()); //value1, 阻塞 } ``` 测试结果: ``` ForkJoinPool.commonPool-worker-3 load cache ---test1 main ========= value1 ``` #### 自动异步加载 - 创建**AsyncLoadingCache** 示例1: ```java @Test public void testAsynchronouslyLoadingCache() throws ExecutionException, InterruptedException { AsyncLoadingCache cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10_000) //异步的封装一段同步操作来生成缓存元素 .buildAsync(new CacheLoader() { @Override public @Nullable String load(@NonNull String key) throws Exception { System.out.println(Thread.currentThread().getName()); // ForkJoinPool.commonPool-worker-3 System.out.println("load cache ---" + key); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return MAP.get(key); } }); CompletableFuture value = cache.get("test1"); System.out.println(Thread.currentThread().getName()); //main System.out.println("========="); System.out.println(value.get()); //value1 阻塞 } ``` 测试结果 ``` ForkJoinPool.commonPool-worker-3 load cache ---test1 main ========= value1 ``` 示例2: ```java @Test public void testAsynchronouslyLoadingCache2() throws ExecutionException, InterruptedException { AsyncLoadingCache cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10_000) //构建一个异步缓存元素操作并返回一个future .buildAsync(new AsyncCacheLoader() { @Override public @NonNull CompletableFuture asyncLoad(@NonNull String key, @NonNull Executor executor) { System.out.println(Thread.currentThread().getName()); //main return CompletableFuture.supplyAsync(() -> { System.out.println("load cache"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); // ForkJoinPool.commonPool-worker-3 return MAP.get(key); }); } }); System.out.println(cache.get("test1").get()); // value1 阻塞 } ``` 测试结果: ``` main load cache ForkJoinPool.commonPool-worker-3 value1 ``` ### 过期策略 #### 基于大小 基于大小的我们前面已经讲到了。也就是通过设置`maximumSize`来进行大小驱逐策略,还有设置`maximumWeight`来设置权重驱逐策略 示例: ```java @Test public void testManualLoadCache6() { Cache cache = Caffeine.newBuilder() .maximumSize(1) .build(); cache.put("key1","value1"); cache.put("key2","value2"); System.out.println(cache.getIfPresent("key1")); System.out.println(cache.getIfPresent("key2")); cache.cleanUp(); System.out.println(cache.getIfPresent("key1")); System.out.println(cache.getIfPresent("key2")); } ``` #### 基于时间 Caffeine提供了三种定时驱逐策略 1. **expireAfterWrite(long, TimeUnit)** - 在最后一次写入缓存后开始计时,在指定的时间后过期。 示例: ```java @DisplayName("基于时间的过期策略,设置expireAfterWrite") @Test public void testManualLoadCache7() throws InterruptedException { //在最后一次写入缓存后开始计时,在指定的时间后过期。 Cache cache = Caffeine.newBuilder() .expireAfterWrite(3,TimeUnit.SECONDS) .build(); cache.put("key1","value1"); Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> null } ``` 2. **expireAfterAccess(long, TimeUnit)** - 在最后一次读或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。 示例: ```java @DisplayName("基于时间的过期策略,设置expireAfterAccess") @Test public void testManualLoadCache8() throws InterruptedException { // 在最后一次读或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。 Cache cache = Caffeine.newBuilder() .expireAfterAccess(3,TimeUnit.SECONDS) .build(); cache.put("key1","value1"); Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(3001); System.out.println(cache.getIfPresent("key1")); // -> null } ``` 3. **expireAfter(Expiry)** - 在expireAfter中需要自己实现Expiry接口,这个接口支持expireAfterCreate,expireAfterUpdate,以及expireAfterRead了之后多久过期。注意这个是和expireAfterAccess、expireAfterAccess是互斥的。这里和expireAfterAccess、expireAfterAccess不同的是,需要你告诉缓存框架,他应该在具体的某个时间过期,获取具体的过期时间。 示例: ```java @DisplayName("基于时间的过期策略,设置expireAfterCreate") @Test public void testManualLoadCache9() throws InterruptedException { Cache cache = Caffeine.newBuilder() .expireAfter(new Expiry() { @Override public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) { return TimeUnit.SECONDS.toNanos(3); } @Override public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) { return currentDuration; } @Override public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) { return currentDuration; } }) .build(); cache.put("key1", "value1"); Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> null } @DisplayName("基于时间的过期策略,设置expireAfterUpdate") @Test public void testManualLoadCache10() throws InterruptedException { Cache cache = Caffeine.newBuilder() .expireAfter(new Expiry() { @Override public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) { return Long.MAX_VALUE; } @Override public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) { return TimeUnit.SECONDS.toNanos(3); } @Override public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) { return currentDuration; } }) .build(); cache.put("key1", "value1"); cache.put("key1", "value2"); Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value2 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value2 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> null } @DisplayName("基于时间的过期策略,设置expireAfterRead") @Test public void testManualLoadCache11() throws InterruptedException { Cache cache = Caffeine.newBuilder() .expireAfter(new Expiry() { @Override public long expireAfterCreate(@NonNull String key, @NonNull String value, long currentTime) { return Long.MAX_VALUE; } @Override public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) { return currentDuration; } @Override public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @NonNegative long currentDuration) { return TimeUnit.SECONDS.toNanos(3); } }) .build(); cache.put("key1", "value1"); Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(1000); System.out.println(cache.getIfPresent("key1")); // -> value1 Thread.sleep(3001); System.out.println(cache.getIfPresent("key1")); // -> null } ``` #### 基于引用 > Caffeine 允许你配置你的缓存去让GC去帮助清理缓存当中的元素,其中key支持弱引用,而value则支持弱引用和软引用。记住 `AsyncCache`不支持软引用和弱引用。 ##### Caffeine.weakKeys() > `Caffeine.weakKeys()` 在保存key的时候将会进行弱引用。这允许在GC的过程中,当key没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 `equals()`去进行key之间的比较。 示例: ```java /** * Caffeine.weakKeys() 在保存key的时候将会进行弱引用。 * 这允许在GC的过程中,当key没有被任何强引用指向的时候去将缓存元素回收。 * 由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 equals()去进行key之间的比较。 */ @Test public void testWeakKeys() { Cache cache = Caffeine.newBuilder() .weakKeys() .build(); cache.put(new String("test"), "value1"); System.out.println(cache.asMap());//{test=value1} System.gc(); System.out.println(cache.asMap()); //value1 } ``` ##### Caffeine.weakValues() > `Caffeine.weakValues()`在保存value的时候将会使用弱引用。这允许在GC的过程中,当value没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 `equals()`去进行value之间的比较。 示例: ```java /** * Caffeine.weakValues()在保存value的时候将会使用弱引用。 * 这允许在GC的过程中,当value没有被任何强引用指向的时候去将缓存元素回收。 * 由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 equals()去进行value之间的比较。 */ @Test public void testWeakValues() { Cache cache = Caffeine.newBuilder() .weakKeys() .weakValues() .build(); cache.put("test1", new String("value")); System.out.println(cache.asMap());//{test1=value} System.out.println(cache.getIfPresent("test1")); //value System.gc(); System.out.println(cache.getIfPresent("test1")); //null System.out.println(cache.asMap());//{} } ``` ##### Caffeine.softValues() > `Caffeine.softValues()`在保存value的时候将会使用软引用。为了相应内存的需要,在GC过程中被软引用的对象将会被通过LRU算法回收。由于使用软引用可能会影响整体性能,我们还是建议通过使用基于缓存容量的驱逐策略代替软引用的使用。同样的,使用 `softValues()` 将会通过引用相等(==)而不是对象相等 `equals()`去进行value之间的比较。 示例: ```java @Test public void testWithoutSoftValues() { Cache cache = Caffeine.newBuilder() .build(); cache.put("test1", new byte[1024 * 1024 * 1024]); cache.put("test2", new byte[1024 * 1024 * 1024]); cache.put("test3", new byte[1024 * 1024 * 1024]); cache.put("test4", new byte[1024 * 1024 * 1024]); //Exception in thread "main" java.lang.OutOfMemoryError: Java heap space System.out.println(cache.asMap()); } ``` ```java @Test public void testSoftValues() { Cache cache = Caffeine.newBuilder() .softValues() .build(); cache.put("test1", new byte[1024 * 1024 * 1024]); cache.put("test2", new byte[1024 * 1024 * 1024]); cache.put("test3", new byte[1024 * 1024 * 1024]); cache.put("test4", new byte[1024 * 1024 * 1024]); System.out.println(cache.asMap());//{test4=[B@5bf0d49} } ``` 如果不使用`softValues`的话,程序会报`OutOfMemoryError`,如果使用了``softValues``则会回收掉缓存 ### 缓存移除 #### invalidate(Object key)方法 示例: ```java @DisplayName("测试移除cache,invalidate(key)方法") @Test public void testRemoveCache() { Cache cache = Caffeine.newBuilder() .expireAfterWrite(50, TimeUnit.SECONDS) .maximumSize(100) .build(); cache.put("test1","value1"); cache.put("test2","value2"); cache.put("test3","value3"); cache.put("test4","value4"); System.out.println(cache.asMap()); //{test1=value1, test4=value4, test2=value2, test3=value3} cache.invalidate("test1"); //移除指定key的Entry System.out.println(cache.asMap()); //{test4=value4, test2=value2, test3=value3} } ``` #### invalidateAll(Iterable keys)方法 示例: ```java @DisplayName("测试移除cache,invalidateAll(keys)方法") @Test public void testRemoveCache3() { Cache cache = Caffeine.newBuilder() .expireAfterWrite(50, TimeUnit.SECONDS) .maximumSize(100) .build(); cache.put("test1","value1"); cache.put("test2","value2"); cache.put("test3","value3"); cache.put("test4","value4"); System.out.println(cache.asMap()); //{test1=value1, test4=value4, test2=value2, test3=value3} cache.invalidateAll(Arrays.asList("test1","test2")); //批量移除指定key的Entry System.out.println(cache.asMap()); //{test4=value4, test3=value3} } ``` #### invalidateAll()方法 示例: ```java @DisplayName("测试移除cache,invalidateAll()方法") @Test public void testRemoveCache2() { Cache cache = Caffeine.newBuilder() .expireAfterWrite(50, TimeUnit.SECONDS) .maximumSize(100) .build(); cache.put("test1","value1"); cache.put("test2","value2"); cache.put("test3","value3"); cache.put("test4","value4"); System.out.println(cache.asMap()); //{test1=value1, test4=value4, test2=value2, test3=value3} cache.invalidateAll(); //移除所有的cache System.out.println(cache.asMap()); //{} } ``` #### 移除监听器(RemovalListener) 示例: ```java @Test public void testRemovalListener() { Cache cache = Caffeine.newBuilder() .expireAfterWrite(50, TimeUnit.SECONDS) .maximumSize(100) .removalListener((RemovalListener) (key, value, cause) -> System.out.println(Thread.currentThread().getName() + "--" + MessageFormat.format("key:[{0}],value:[{1}],cause:[{2}]",key,value,cause))) .build(); cache.put("test1", "value1"); cache.put("test2", "value2"); cache.put("test3", "value3"); cache.put("test4", "value4"); System.out.println(cache.asMap()); //{test1=value1, test4=value4, test2=value2, test3=value3} cache.invalidate("test1"); //移除指定key的Entry System.out.println(cache.asMap()); //{test4=value4, test2=value2, test3=value3} //removalListener打印:ForkJoinPool.commonPool-worker-3--key:[test1],value:[value1],cause:[EXPLICIT] } @Test public void testRemovalListener2() { Cache cache = Caffeine.newBuilder() .expireAfterWrite(50, TimeUnit.SECONDS) .maximumSize(1) .removalListener((RemovalListener) (key, value, cause) -> System.out.println(MessageFormat.format("key:[{0}],value:[{1}],cause:[{2}]",key,value,cause))) .build(); cache.put("test1", "value1"); cache.put("test2", "value2"); System.out.println(cache.asMap()); //{test1=value1, test4=value4, test2=value2, test3=value3} cache.cleanUp(); System.out.println(cache.asMap()); //{test4=value4, test2=value2, test3=value3} //removalListener打印:key:[test1],value:[value1],cause:[SIZE] } @Test public void testRemovalListener3() throws InterruptedException { Cache cache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .maximumSize(10) .removalListener((RemovalListener) (key, value, cause) -> System.out.println(MessageFormat.format("key:[{0}],value:[{1}],cause:[{2}]",key,value,cause))) .build(); cache.put("test1", "value1"); System.out.println(cache.asMap()); //{test1=value1} Thread.sleep(1000); cache.cleanUp(); System.out.println(cache.asMap()); //{} //removalListener打印:key:[test1],value:[value1],cause:[EXPIRED] } ``` ### Writer 我们还可以通过设置 Writer,将对于缓存的更新,作用于其他存储,例如数据库。 示例: ```java @Test public void testWriter() { Cache cache = Caffeine.newBuilder() .expireAfterWrite(50, TimeUnit.SECONDS) .maximumSize(100) .writer(new CacheWriter() { @Override public void write(@NonNull String key, @NonNull String value) { // 持久化或者次级缓存 System.out.println(MessageFormat.format("key:[{0}],value:[{1}]", key, value)); } @Override public void delete(@NonNull String key, @Nullable String value, @NonNull RemovalCause cause) { // 从持久化或者次级缓存中删除 System.out.println(MessageFormat.format("key:[{0}],value:[{1}],cause:[{2}]", key, value, cause)); } }) .build(); cache.put("test1", "value1"); cache.put("test2", "value2"); System.out.println("==========="); System.out.println(cache.asMap()); cache.invalidate("test1"); System.out.println(cache.asMap()); cache.put("test2", "value222"); /** * 打印结果: * key:[test1],value:[value1] * key:[test2],value:[value2] * =========== * {test1=value1, test2=value2} * key:[test1],value:[value1],cause:[EXPLICIT] * {test2=value2} * key:[test2],value:[value222] */ } ``` ### 统计 > 通过使用`Caffeine.recordStats()`方法可以打开数据收集功能。`Cache.stats()`方法将会返回一个`CacheStats`对象,其将会含有一些统计指标,比如: > > - `hitRate():` 查询缓存的命中率 > - `evictionCount():` 被驱逐的缓存数量 > - `averageLoadPenalty():` 新值被载入的平均耗时 示例: ```java @Test public void testRecordStats() { Cache cache = Caffeine.newBuilder() .maximumSize(1) //自定义数据采集器 .recordStats(() -> new StatsCounter() { @Override public void recordHits(@NonNegative int count) { System.out.println("recordHits:" + count); } @Override public void recordMisses(@NonNegative int count) { System.out.println("recordMisses:" + count); } @Override public void recordLoadSuccess(@NonNegative long loadTime) { System.out.println("recordLoadSuccess:" + loadTime); } @Override public void recordLoadFailure(@NonNegative long loadTime) { System.out.println("recordLoadFailure:" + loadTime); } @Override public void recordEviction() { System.out.println("recordEviction..."); } @Override public @NonNull CacheStats snapshot() { return null; } }) .build(); cache.put("test1", "value1"); cache.put("test2", "value2"); System.out.println(cache.asMap()); cache.getIfPresent("test1"); cache.getIfPresent("test3"); cache.cleanUp(); System.out.println(cache.asMap()); /** * 打印结果: * {test1=value1, test2=value2} * recordHits:1 * recordMisses:1 * recordEviction... * {test2=value2} */ } @Test public void testRecordStats2() { LoadingCache asyncCache = Caffeine.newBuilder() .maximumSize(1) //自定义数据采集器 .recordStats(() -> new StatsCounter() { @Override public void recordHits(@NonNegative int count) { System.out.println("recordHits:" + count); } @Override public void recordMisses(@NonNegative int count) { System.out.println("recordMisses:" + count); } @Override public void recordLoadSuccess(@NonNegative long loadTime) { System.out.println("recordLoadSuccess:" + loadTime); } @Override public void recordLoadFailure(@NonNegative long loadTime) { System.out.println("recordLoadFailure:" + loadTime); } @Override public void recordEviction() { System.out.println("recordEviction..."); } @Override public @NonNull CacheStats snapshot() { return null; } }) .build(new CacheLoader() { @Override public @Nullable String load(@NonNull String key) throws Exception { return MAP.get(key); } }); asyncCache.get("test1"); System.out.println(asyncCache.asMap()); /** * 打印: * recordMisses:1 * recordLoadSuccess:19800 * {test1=value1} */ } @Test public void testRecordStats3() { LoadingCache asyncCache = Caffeine.newBuilder() .maximumSize(1) //自定义数据采集器 .recordStats(() -> new StatsCounter() { @Override public void recordHits(@NonNegative int count) { System.out.println("recordHits:" + count); } @Override public void recordMisses(@NonNegative int count) { System.out.println("recordMisses:" + count); } @Override public void recordLoadSuccess(@NonNegative long loadTime) { System.out.println("recordLoadSuccess:" + loadTime); } @Override public void recordLoadFailure(@NonNegative long loadTime) { System.out.println("recordLoadFailure:" + loadTime); } @Override public void recordEviction() { System.out.println("recordEviction..."); } @Override public @NonNull CacheStats snapshot() { return null; } }) .build(new CacheLoader() { @Override public @Nullable String load(@NonNull String key) throws Exception { throw new RuntimeException("failed"); } }); asyncCache.get("test1"); System.out.println(asyncCache.asMap()); /** * 打印: * recordMisses:1 * recordLoadFailure:41100 */ } @Test public void testRecordStats4() { Cache cache = Caffeine.newBuilder() .maximumSize(1) //打开数据采集 .recordStats() .build(); cache.put("test1", "value1"); cache.put("test2", "value2"); System.out.println(cache.asMap());//{test1=value1, test2=value2} cache.getIfPresent("test1"); cache.getIfPresent("test3"); cache.cleanUp(); System.out.println(cache.asMap());//{test2=value2} System.out.println(cache.stats().hitRate());//查询缓存的命中率 0.5 System.out.println(cache.stats().hitCount());//命中次数 1 System.out.println(cache.stats().evictionCount());//被驱逐的缓存数量 1 System.out.println(cache.stats().averageLoadPenalty());//新值被载入的平均耗时 /** * 打印结果: * {test1=value1, test2=value2} * {test2=value2} * 0.5 * 1 * 1 * 0.0 */ } @Test public void testRecordStats5() { LoadingCache asyncCache = Caffeine.newBuilder() .maximumSize(1) //打开数据采集 .recordStats() .build(new CacheLoader() { @Override public @Nullable String load(@NonNull String key) throws Exception { return MAP.get(key); } }); asyncCache.get("test1"); asyncCache.get("test1"); System.out.println(asyncCache.asMap());//{test1=value1} System.out.println(asyncCache.stats().hitRate());//查询缓存的命中率 0.5 System.out.println(asyncCache.stats().hitCount());//命中次数 1 System.out.println(asyncCache.stats().evictionCount());//被驱逐的缓存数量 0 System.out.println(asyncCache.stats().averageLoadPenalty());//新值被载入的平均耗时 21100.0 /** * 打印: * {test1=value1} * 0.5 * 1 * 0 * 21100.0 */ } ``` ## 参考 [Java本地缓存框架系列-Caffeine-1. 简介与使用](https://zhuanlan.zhihu.com/p/135971092) [本地缓存高性能之王Caffeine](https://zhuanlan.zhihu.com/p/142667371) [Introduction to Caffeine](https://www.baeldung.com/java-caching-caffeine) [https://www.javadoc.io/doc/com.github.ben-manes.caffeine/caffeine](https://www.javadoc.io/doc/com.github.ben-manes.caffeine/caffeine) [https://github.com/ben-manes/caffeine/wiki](https://github.com/ben-manes/caffeine/wiki) [本地缓存性能之王Caffeine](https://mp.weixin.qq.com/s/PCJkLWr3rBVJkSPGOHQc0Q) [Caffeine Cache 进程缓存之王](https://www.jianshu.com/p/15d0a9ce37dd) [Caffeine缓存](https://www.jianshu.com/p/9a80c662dac4) [https://github.com/ben-manes/caffeine](https://github.com/ben-manes/caffeine) [Caffeine Cache~高性能 Java 本地缓存之王](https://cloud.tencent.com/developer/article/1684655) [一文深入了解史上最强的Java堆内缓存框架Caffeine](https://www.debugger.wiki/article/html/1584249784088171) [干掉GuavaCache:Caffeine才是本地缓存的王](https://mp.weixin.qq.com/s/_kJVEWN97UO49MhHoZKZnA) [SpringBoot 使用 Caffeine 本地缓存](https://mp.weixin.qq.com/s/IEKh2njCs75wE6tLTNpPtA)