Infinispan Cache

默认情况下,Quarkus Cache 使用 Caffeine 作为后端。你可以改用 Infinispan。

By default, Quarkus Cache uses Caffeine as backend. It’s possible to use Infinispan instead. Unresolved directive in cache-infinispan-reference.adoc - include::{includes}/extension-status.adoc[]

Infinispan as cache backend

将 Infinispan 用作 Quarkus 缓存的后端时,每个缓存项都将存储在 Infinispan 中:

When using Infinispan as the backend for Quarkus cache, each cached item will be stored in Infinispan:

  • The backend uses the <default> Infinispan client (unless configured differently), so ensure its configuration is set up accordingly (or use the Infinispan Dev Service)

  • Both the key and the value are marshalled using Protobuf with Protostream.

Use the Infinispan backend

首先,将 @15 扩展添加到你的项目:

First, add the quarkus-infinispan-cache extension to your project:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-infinispan-cache</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-infinispan-cache")

然后,使用 @16 和其他缓存注释,正如 @17 中详细说明的那样:

Then, use the @CacheResult and other cache annotations as detailed in the Quarkus Cache guide:

@GET
@Path("/{keyElement1}/{keyElement2}/{keyElement3}")
@CacheResult(cacheName = "expensiveResourceCache")
public ExpensiveResponse getExpensiveResponse(@PathParam("keyElement1") @CacheKey String keyElement1,
        @PathParam("keyElement2") @CacheKey String keyElement2, @PathParam("keyElement3") @CacheKey String keyElement3,
        @QueryParam("foo") String foo) {
    invocations.incrementAndGet();
    ExpensiveResponse response = new ExpensiveResponse();
    response.setResult(keyElement1 + " " + keyElement2 + " " + keyElement3 + " too!");
    return response;
}

@POST
@CacheInvalidateAll(cacheName = "expensiveResourceCache")
public void invalidateAll() {

}

Configure the Infinispan backend

Infinispan 后端使用 @18 Infinispan 客户端. 请参阅 @19 以了解配置对 Infinispan 的访问权限。

The Infinispan backend uses the <default> Infinispan client. Refer to the Infinispan reference for configuring the access to Infinispan.

在开发模式中,你可以使用 @20.

In dev mode, you can use the Infinispan Dev Service.

如果你想为你的缓存使用另一个 Infinispan,请按照如下方式配置 @21:

If you want to use another Infinispan for your cache, configure the client-name as follows:

quarkus.cache.infinispan.client-name=another

Marshalling

在 Quarkus 中与 Infinispan 交互时,您可以在存储或从缓存中检索数据时轻松地序列化和反序列化 Java 原始类型。Infinispan 不需要额外的编组配置。

When interacting with Infinispan in Quarkus, you can easily serialize and deserialize Java primitive types when storing or retrieving data from the cache. No additional marshalling configuration is required for Infinispan.

@CacheResult(cacheName = "weather-cache") (1)
public String getDailyForecast(String dayOfWeek, int dayOfMonth, String city) { (2)
    return dayOfWeek + " will be " + getDailyResult(dayOfMonth % 4) + " in " + city;
}
1 Ask this method execution to be cached in the 'weather-cache'
2 The key combines String dayOfWeek, int dayOfMonth and String city. The associated value is of type String.

Quarkus 默认使用协议缓冲区对 Infinispan 中的数据进行序列化。Infinispan 建议使用协议缓冲区作为首选的数据结构方式。因此,在使用纯旧 Java 对象 (POJO) 时,用户需要在 Infinispan 中提供编组架构。

Quarkus uses Protobuf for data serialization in Infinispan by default. Infinispan recommends using Protobuf as the preferred way to structure data. Therefore, when working with Plain Old Java Objects (POJOs), users need to supply the schema for marshalling in Infinispan.

Marshalling Java types

我们假设要使用 `java.time.LocalDate`创建一个复合键。

Let’s say we want a composite Key using java.time.LocalDate.

@CacheResult(cacheName = "weather-cache") (1)
public String getDailyForecast(LocalDate date, String city) { (2)
    return date.getDayOfWeek() + " will be " + getDailyResult(date.getDayOfMonth() % 4) + " in " + city;
}
1 Request that the response of this method execution be cached in 'weather-cache'
2 The key combines a java.util.LocalDate date and a String city. The associated value is of type 'String'.

由于 Infinispan 在 Quarkus 中默认使用协议缓冲区序列化数据,因此执行代码会产生以下错误:

Since Infinispan serializes data by default using Protobuf in Quarkus, executing the code will result in the following error:

java.lang.IllegalArgumentException:
No marshaller registered for object of Java type java.time.LocalDate

协议缓冲区本身不知道如何编组 java.time.LocalDate。幸运的是,Protostream 提供了一种使用 `@ProtoAdapter`和 `@ProtoSchema`来轻松解决此问题的方案。

Protobuf does not inherently know how to marshal java.time.LocalDate. Fortunately, Protostream provides a straightforward solution to this problem using @ProtoAdapter and @ProtoSchema.

@ProtoAdapter(LocalDate.class)
public class LocalDateAdapter {
    @ProtoFactory
    LocalDate create(String localDate) {
        return LocalDate.parse(localDate);
    }

    @ProtoField(1)
    String getLocalDate(LocalDate localDate) {
        return localDate.toString();
    }
}

@ProtoSchema(includeClasses = LocalDateAdapter.class, schemaPackageName = "quarkus")
public interface Schema extends GeneratedSchema {
}

Marshalling your POJOs

与 Java 类型一样,在将您的 POJO 用作键或值时,您可以遵循此方法:

Just like with Java types, when using your POJOs as keys or values, you can follow this approach:

@CacheResult(cacheName = "my-cache") (1)
public ExpensiveResponse requestApi(String id) { (2)
    // very expensive call

    return new ExpensiveResponse(...);
}
1 Request that the response of this method execution be cached in 'my-cache'
2 The key is a String. The associated value is of type org.acme.ExpensiveResponse.

在这种情况下,您需要使用 `@Proto`和 `@ProtoSchema`为 Java 类型定义架构。该架构可以包含 Quarkus 应用程序中使用的所有类型和适配器。

In this case, you’ll need to define the schema for your Java type using @Proto and @ProtoSchema. This schema can encompass all types and adapters used in your Quarkus application.

@Proto
public record ExpensiveResponse(String result) {
}

@ProtoSchema(includeClasses = { ExpensiveResponse.class })
interface Schema extends GeneratedSchema {
}

在基于注解的序列化部分的 Infinispan reference中阅读更多相关信息。

Read more about it in the Infinispan reference in the Annotation based serialization section.

Expiration

您可以配置两个数据过期属性: lifespan*和 *max-idle

You have the option to configure two properties for data expiration: lifespan and max-idle.

Lifespan

在 Infinispan 中,*lifespan*指一个配置参数,用于确定一项(或一个对象)在缓存中创建或在标记为过期并从缓存中移除之前,在该缓存中可以驻留的最长时间。

In Infinispan, lifespan refers to a configuration parameter that determines the maximum time an entry (or an object) can remain in the cache since it was created or last accessed before it is considered expired and removed from the cache.

当您为 Infinispan 缓存中的条目配置 *lifespan*参数时,您指定了一个时间持续时间。一项被添加到缓存中或被访问(读取或写入)后,便开始倒计时其生命周期。如果自条目创建或最后访问以来的时间超出了指定“生命周期”持续时间,那么该条目将被视为已过期,且符合从缓存中驱逐的条件。

When you configure the lifespan parameter for entries in an Infinispan cache, you specify a time duration. After an entry has been added to the cache or accessed (read or written), it starts its lifespan countdown. If the time since the entry was created or last accessed exceeds the specified "lifespan" duration, the entry is considered expired and becomes eligible for eviction from the cache.

quarkus.cache.infinispan.my-cache.lifespan=10s

Max Idle

当您为 Infinispan 缓存中的条目配置 *max-idle*参数时,您指定了一个时间持续时间。一项在缓存中被访问(读取或写入)后,如果在指定持续时间内没有后续访问该项,那么该项会被视为闲置。一旦闲置时间超过 *max-idle*持续时间,那么该条目将被视为已过期,且符合从缓存中驱逐的条件。

When you configure the max-idle parameter for entries in an Infinispan cache, you specify a time duration. After an entry has been accessed (read or written) in the cache, if there are no subsequent accesses to that entry within the specified duration, it is considered idle. Once the idle time exceeds the max-idle duration, the entry is considered expired and eligible for eviction from the cache.

quarkus.cache.infinispan.my-cache.max-idle=100s

Unresolved directive in cache-infinispan-reference.adoc - include::{generated-dir}/config/quarkus-infinispan-cache.adoc[]