失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > java caffeine_高性能 Java 缓存库 — Caffeine

java caffeine_高性能 Java 缓存库 — Caffeine

时间:2023-12-06 20:45:03

相关推荐

java caffeine_高性能 Java 缓存库 — Caffeine

1、介绍

在本文中,我将介绍 Caffeine — 一个高性能的 Java 缓存库。

缓存和 Map 之间的一个根本区别在于缓存可以回收存储的 item。

回收策略为在指定时间删除哪些对象。此策略直接影响缓存的命中率 —— 缓存库的一个重要特性。

Caffeine 因使用了 Window TinyLfu 回收策略,提供了一个近乎最佳的命中率。

2、依赖

我们需要在 pom.xml 中添加 caffeine 依赖:

1

2

3

4

5

com.github.ben-manes.caffeine

caffeine

2.5.5

你可以在 Maven Central 上找到最新版本的 caffeine。

3、填充缓存

让我们来了解一下 Caffeine 的三种缓存填充策略:手动、同步加载和异步加载。

首先,我们为要缓存中存储的值类型写一个类:

1

2

3

4

5

6

7

8

9

10

11class DataObject{

private final String data;

private static int objectCounter = 0;

// standard constructors/getters

public static DataObject get(String data){

objectCounter++;

return new DataObject(data);

}

}

3.1、手动填充

在此策略中,我们手动将值放入缓存后再检索。

初始化缓存:

1

2

3

4Cache cache = Caffeine.newBuilder()

.expireAfterWrite(1, TimeUnit.MINUTES)

.maximumSize(100)

.build();

现在,我们可以使用 getIfPresent 方法从缓存中获取值。如果缓存中不存指定的值,则方法将返回 null:

1

2

3

4String key = "A";

DataObject dataObject = cache.getIfPresent(key);

assertNull(dataObject);

我们可以使用 put 方法手动填充缓存:

1

2

3

4cache.put(key, dataObject);

dataObject = cache.getIfPresent(key);

assertNotNull(dataObject);

我们也可以使用 get 方法获取值,该方法将一个参数为 key 的 Function 作为参数传入。如果缓存中不存在该 key,则该函数将用于提供默认值,该值在计算后插入缓存中:

1

2

3

4

5dataObject = cache

.get(key, k -> DataObject.get("Data for A"));

assertNotNull(dataObject);

assertEquals("Data for A", dataObject.getData());

get 方法可以以原子方式执行计算。这意味着你只进行一次计算 —— 即使有多个线程同时请求该值。这就是为什么使用 get 要优于 getIfPresent。

有时我们需要手动触发一些缓存的值失效:

1

2

3

4cache.invalidate(key);

dataObject = cache.getIfPresent(key);

assertNull(dataObject);

3.2、同步加载

这种加载缓存的方式使用了与用于初始化值的 Function 的手动策略类似的 get 方法。让我们看看如何使用它。

首先,我们需要初始化缓存:

1

2

3

4LoadingCache cache = Caffeine.newBuilder()

.maximumSize(100)

.expireAfterWrite(1, TimeUnit.MINUTES)

.build(k -> DataObject.get("Data for " + k));

现在我们可以使用 get 方法来检索值:

1

2

3

4DataObject dataObject = cache.get(key);

assertNotNull(dataObject);

assertEquals("Data for " + key, dataObject.getData());

当然,也可以使用 getAll 方法获取一组值:

1

2

3

4Map dataObjectMap

= cache.getAll(Arrays.asList("A", "B", "C"));

assertEquals(3, dataObjectMap.size());

从传给 build 方法的初始化函数检索值,这使得可以使用缓存作为访问值的主要门面(Facade)。

3.3、异步加载

此策略的作用与之前相同,但是以异步方式执行操作,并返回一个包含值的 CompletableFuture:

1

2

3

4AsyncLoadingCache cache = Caffeine.newBuilder()

.maximumSize(100)

.expireAfterWrite(1, TimeUnit.MINUTES)

.buildAsync(k -> DataObject.get("Data for " + k));

我们可以以相同的方式使用 get 和 getAll 方法,同时考虑到他们返回的是 CompletableFuture:

1

2

3

4

5

6

7

8

9String key = "A";

cache.get(key).thenAccept(dataObject -> {

assertNotNull(dataObject);

assertEquals("Data for " + key, dataObject.getData());

});

cache.getAll(Arrays.asList("A", "B", "C"))

.thenAccept(dataObjectMap -> assertEquals(3, dataObjectMap.size()));

CompletableFuture 有许多有用的 API,你可以在此文中获取更多内容。

4、值回收

Caffeine 有三个值回收策略:基于大小,基于时间和基于引用。

4.1、基于大小回收

这种回收方式假定当缓存大小超过配置的大小限制时会发生回收。 获取大小有两种方法:缓存中计数对象,或获取权重。

让我们看看如何计算缓存中的对象。当缓存初始化时,其大小等于零:

1

2

3

4

5LoadingCache cache = Caffeine.newBuilder()

.maximumSize(1)

.build(k -> DataObject.get("Data for " + k));

assertEquals(0, cache.estimatedSize());

当我们添加一个值时,大小明显增加:

1

2

3cache.get("A");

assertEquals(1, cache.estimatedSize());

我们可以将第二个值添加到缓存中,这将导致第一个值被删除:

1

2

3

4cache.get("B");

cache.cleanUp();

assertEquals(1, cache.estimatedSize());

值得一提的是,在获取缓存大小之前,我们调用了 cleanUp 方法。这是因为缓存回收被异步执行,这种方式有助于等待回收工作完成。

我们还可以传递一个 weigher Function 来获取缓存的大小:

1

2

3

4

5

6

7

8

9

10

11

12LoadingCache cache = Caffeine.newBuilder()

.maximumWeight(10)

.weigher((k,v) -> 5)

.build(k -> DataObject.get("Data for " + k));

assertEquals(0, cache.estimatedSize());

cache.get("A");

assertEquals(1, cache.estimatedSize());

cache.get("B");

assertEquals(2, cache.estimatedSize());

当 weight 超过 10 时,值将从缓存中删除:

1

2

3

4cache.get("C");

cache.cleanUp();

assertEquals(2, cache.estimatedSize());

4.2、基于时间回收

这种回收策略是基于条目的到期时间,有三种类型:

访问后到期 — 从上次读或写发生后,条目即过期。

写入后到期 — 从上次写入发生之后,条目即过期

自定义策略 — 到期时间由 Expiry 实现独自计算

让我们使用 expireAfterAccess 方法配置访问后过期策略:

1

2

3LoadingCache cache = Caffeine.newBuilder()

.expireAfterAccess(5, TimeUnit.MINUTES)

.build(k -> DataObject.get("Data for " + k));

要配置写入后到期策略,我们使用 expireAfterWrite 方法:

1

2

3

4

5cache = Caffeine.newBuilder()

.expireAfterWrite(10, TimeUnit.SECONDS)

.weakKeys()

.weakValues()

.build(k -> DataObject.get("Data for " + k));

要初始化自定义策略,我们需要实现 Expiry 接口:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17cache = Caffeine.newBuilder().expireAfter(new Expiry() {

@Override

public long expireAfterCreate(

String key, DataObject value, long currentTime){

return value.getData().length() * 1000;

}

@Override

public long expireAfterUpdate(

String key, DataObject value, long currentTime, long currentDuration){

return currentDuration;

}

@Override

public long expireAfterRead(

String key, DataObject value, long currentTime, long currentDuration){

return currentDuration;

}

}).build(k -> DataObject.get("Data for " + k));

4.3、基于引用回收

我们可以将缓存配置启用基于缓存键值的垃圾回收。为此,我们将 key 和 value 配置为 弱引用,并且可以仅配置软引用以进行垃圾回收。

当对象的没有任何强引用时,使用 WeakRefence 可以启用对象的垃圾收回收。SoftReference 允许对象根据 JVM 的全局最近最少使用(Least-Recently-Used)的策略进行垃圾回收。有关 Java 引用的更多详细信息,请参见此处。

我们应该使用 Caffeine.weakKeys()、Caffeine.weakValues() 和 Caffeine.softValues() 来启用每个选项:

1

2

3

4

5

6

7

8

9

10LoadingCache cache = Caffeine.newBuilder()

.expireAfterWrite(10, TimeUnit.SECONDS)

.weakKeys()

.weakValues()

.build(k -> DataObject.get("Data for " + k));

cache = Caffeine.newBuilder()

.expireAfterWrite(10, TimeUnit.SECONDS)

.softValues()

.build(k -> DataObject.get("Data for " + k));

5、刷新

可以将缓存配置为在指定时间段后自动刷新条目。让我们看看如何使用 refreshAfterWrite 方法:

1

2

3Caffeine.newBuilder()

.refreshAfterWrite(1, TimeUnit.MINUTES)

.build(k -> DataObject.get("Data for " + k));

这里我们要明白 expireAfter 和 refreshAfter 之间的区别。当请求过期条目时,执行将发生阻塞,直到 build Function 计算出新值为止。

但是,如果条目可以刷新,则缓存将返回一个旧值,并异步重新加载该值。

6、统计

Caffeine 有记录缓存使用情况的统计方式:

1

2

3

4

5

6

7

8

9LoadingCache cache = Caffeine.newBuilder()

.maximumSize(100)

.recordStats()

.build(k -> DataObject.get("Data for " + k));

cache.get("A");

cache.get("A");

assertEquals(1, cache.stats().hitCount());

assertEquals(1, cache.stats().missCount());

我们也可以传入 recordStats supplier,创建一个 StatsCounter 的实现。每次与统计相关的更改将推送此对象。

7、结论

在本文中,我们熟悉了 Java 的 Caffeine 缓存库,学习了如何配置和填充缓存,以及如何根据自己的需要选择适当的到期或刷新策略。

文中示例的源代码可以在 Github 上找到。

本作品采用知识共享署名 4.0 国际许可协议进行许可,转载请保留原文和译文链接及作者版本信息。

如果觉得《java caffeine_高性能 Java 缓存库 — Caffeine》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。