我是靠谱客的博主 发嗲大米,这篇文章主要介绍高性能缓存Caffeine的基本使用方式介绍性能比对使用方式Caffeine在SpringBoot中的使用,现在分享给大家,希望可以做个参考。

文章目录

  • 介绍
  • 性能比对
  • 使用方式
    • 一、Population(缓存类型)
      • 1.Cache
      • 2.Loading
      • 3.Asynchronous (Manual)
      • 4.Asynchronously Loading
    • 二、Eviction(驱除策略)
      • 1.Size-based(基于容量)
      • 2.Time-based(基于时间)
      • 3.Reference-based(基于引用)
    • 三、Explicit Removals(显示删除)
      • 1.直接删除
      • 2.可监听删除
    • 四、Refresh(刷新机制)
    • 五、Statistics(统计)
  • Caffeine在SpringBoot中的使用

介绍

Caffeine是基于JDK1.8版本的高性能本地缓存库,它是Guava的增强版,与ConcurrentLinkedHashMap相似,支持并发,并且可以在O(1)的时间复杂度内查找、写入元素。

性能比对

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用方式

一、Population(缓存类型)

1.Cache

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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"; }

输出结果:

复制代码
1
2
3
4
5
cache.getIfPresent(key) ---> null cache.get(key, k -> create(key)) ---> hello world cache.put(key, str) ---> hello world cache.invalidate(key) ---> null

2.Loading

LoadingCache是附加在CacheLoader之上构建的缓存对象。

可以使用getAll方法执行批量查找,默认情况下,getAll()方法会单独调用CacheLoader.load()方法来加载每个不在缓存中的Key,必要情况下可以重写CacheLoader.loadAll()方法来弥补其缺陷。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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"; }

输出结果

复制代码
1
2
3
cache.get(key) ---> hello world cache.getAll(keys) ---> {a=a world, b=b world, c=c world, d=d world, e=e world}

3.Asynchronous (Manual)

AsyncCache就是Cache的异步实现方式,提供了通过Executor生成缓存元素并返回CompletableFuture的能力。
synchronous()提供了在缓存计算完成前的阻塞能力,AsyncCache默认使用ForkJoinPool.commonPool()线程池,你也可以通过重写Caffeine.executor(executor)来实现自己的线程池。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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); }

4.Asynchronously Loading

AsyncLoadingCache就是LoadingCache的异步形式

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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); }

二、Eviction(驱除策略)

1.Size-based(基于容量)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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)); }

2.Time-based(基于时间)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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)); }

3.Reference-based(基于引用)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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)); }

三、Explicit Removals(显示删除)

1.直接删除

复制代码
1
2
3
4
5
6
7
// 直接删除 cache.invalidate(key) // 批量删除 cache.invalidateAll(keys) // 删除所有 cache.invalidateAll()

2.可监听删除

监听缓存元素被删除或者被驱除事件

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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(刷新机制)

refresh只有在LoadingCache或者AsyncLoadingCache时才能使用,与驱逐不同之处,在刷新的时候,如果访问元素仍然可以返回,但返回的是旧值。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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(); } } }

输出结果

复制代码
1
2
3
4
5
6
>* >* >** >*** >****

五、Statistics(统计)

caffeine提供了完善的缓存统计能力,提供了metrics-caffeine类库,可以直接入Prometheus

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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()); }

输出结果:

复制代码
1
2
3
4
5
CacheStats{hitCount=15, missCount=6, loadSuccessCount=1, loadFailureCount=0, totalLoadTime=1014390300, evictionCount=0, evictionWeight=0} 命中率:0.7142857142857143 未命中率:0.2857142857142857 加载新值花费的平均时间:1.0143903E9
复制代码
1
2
3
4
5
6
7
8
9
10
11
hitRate:命中率 hitCount:命中次数 missRate:未命中率 missCount:未命中次数 loadSuccessCount:成功加载新值的次数 loadFailureCount:失败加载新值的次数 totalLoadTime:总计加载的耗时 averageLoadPenalty:加载新值花费的平均时间 evictionCount:驱逐的条目数 evictionWeight:按权重驱除的次数

Caffeine在SpringBoot中的使用

配置类

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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(); } }

测试类

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@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); } } }

测试结果

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
命中率: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

最后

以上就是发嗲大米最近收集整理的关于高性能缓存Caffeine的基本使用方式介绍性能比对使用方式Caffeine在SpringBoot中的使用的全部内容,更多相关高性能缓存Caffeine内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(62)

评论列表共有 0 条评论

立即
投稿
返回
顶部