介绍新的 Redis API - 如何使用 Redis 进行缓存?
在 Quarkus 2.11 中,我们引入了一个新的 Redis 交互 API。Redis 数据源 API 旨在更简单、更广泛、类型安全。底层采用高性能的非阻塞客户端(如果您喜欢低级 API,也可以使用它)。
在本篇博文中,我们将介绍这个新 API,并使用它构建一个缓存,这是 Redis 的主要用例之一。
什么是 Redis?
Redis 是一个开源的内存数据存储,可用作数据库、缓存、流引擎和消息代理。Redis 常用于实时数据存储、缓存后端、地理空间实体的数据存储等。要与 Redis 交互,您需要发出命令并接收响应。这些命令以键为目标并操作相关数据。有许多命令分为几组,包括
-
用于操作位向量的 BitMap 组
-
用于操作键的 Generic 组
-
用于操作地理位置项的 Geospatial 组
-
用于操作
field -> item
对集合(如 Java 中的Map
)的 Hash 组 -
用于存储项的列表、集合和有序集合的 List、Set 和 Sorted Set 组
-
用于在频道上发送消息并接收消息的 Pub/Sub 组
-
用于操作值的 String 组(在 Redis 中,Strings 表示值,包括二进制、数字等)
-
用于执行事务的 Transaction 组
您可以在 Redis 命令页面 上找到完整的命令列表。
新的 Quarkus Redis API
新的 Quarkus Redis API 的入口点是两个数据源接口
-
io.quarkus.redis.datasource.RedisDataSource
- 命令式(阻塞)API -
io.quarkus.redis.datasource.ReactiveRedisDataSource
- 响应式 API
如上所述,这些 API 是在低级客户端之上实现的

数据源 API 遵循命令组结构。对于每个组,您都可以检索一个专门用于发出该组命令的对象。在这方面,这个新 API 并不是对 Redis 的抽象。您仍然需要知道您需要的命令。
例如,要操作一个Set<Person>
,您将使用以下代码
record Person(String firstName, String lastName) {}
@ApplicationScoped
class PersonService {
private final SetCommands<String, Person> commands;
public PersonService(RedisDataSource ds) {
// Retrieve the `set` group
commands = ds.set(Person.class);
}
public void add(Person person) {
// Emit the `sadd` command
commands.sadd("key", person);
}
}
API 会为您处理序列化和反序列化。目前,它使用 JSON(通过 Jackson)处理对象,但很快 API 将提供更高级的功能。
此示例使用命令式 API,但响应式 API 对称。
实现 Redis 缓存
是时候编写更连贯的代码了。让我们想象一下以下的 应用程序

我们有一个存储Heroes
的数据库,数量非常多。您需要根据等级返回最强大的 3 位英雄。当然,您可以使用您的 SQL 达人技能,但让我们想象一下,这段代码写于很久以前,无法更改,并且非常耗时。
// Dumb approach, don't do this
return new Ranking(Hero.<Hero>listAll()
.stream()
.sorted((o1, o2) -> Integer.compare(o2.level, o1.level))
.peek(h -> {
// do something very long...
nap();
})
.limit(3)
.collect(Collectors.toList()));
因此,为了避免每次调用都重新计算这组英雄,一种解决方案是将结果缓存一段时间,比如说 10 秒。在这种情况下,可以接受返回可能过时的结果集。
要使用新的 Redis API,我们需要使用 `redis-client` 扩展。对于旧 API 的用户来说,这是同一个扩展。旧 API 仍然可用,但已被弃用,我们计划在某个时候将其删除。
现在我们可以使用 `RedisDataSource` 了,我们可以按如下方式实现 MyRedisCache
类
package me.escoffier.quarkus.supes;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.string.SetArgs;
import io.quarkus.redis.datasource.string.StringCommands;
import javax.enterprise.context.ApplicationScoped;
import java.time.Duration;
import java.util.function.Supplier;
@ApplicationScoped
public class MyRedisCache {
private final StringCommands<String, Ranking> commands;
public MyRedisCache(RedisDataSource ds) {
this.commands = ds.string(Ranking.class);
}
public Ranking get(String key) {
return commands.get(key);
}
public void set(String key, Ranking result) {
commands.set(key, result, new SetArgs().ex(Duration.ofSeconds(10)));
}
public void evict(String key) {
commands.getdel(key);
}
public Ranking getOrSetIfAbsent(String key,
Supplier<Ranking> computation) {
var cached = get(key);
if (cached != null) {
return cached;
} else {
var result = computation.get();
set(key, result);
return result;
}
}
}
请注意,这是一个简单的缓存,没有花哨的功能。Redis 提供了更高级的命令来实现更复杂的策略。
构造函数接收 `RedisDataSource` 并获取一个用于操作 Redis值的对象。在我们的例子中,是 Ranking
(排名前 3 的英雄)
`get` 方法发出 Redis `get` 命令以检索已存储的 `Ranking`(如果不存在,则为 `null`)。
`set` 方法发出 Redis `set` 命令并将 `Ranking` 存储到传入的键中。该命令还配置了过期时间。因此,10 秒后,该值将由 Redis 删除。如上所述,Ranking 实例被序列化为 JSON 文档。
`evict` 方法允许删除存储的值。多个命令可以做到这一点,例如 `del` 或 `getdel`(它也返回存储的值)。
对于我们的应用程序,我们需要更高级一些的功能。我们希望检查 Redis 中是否存在某个值。如果存在,则使用该值;如果不存在,则计算该值并存储它。这在 `getOrSetIfAbsent` 中实现。
现在,我们可以使用这个缓存来避免每次调用都进行繁重的计算(查看 HeroService 类以查看完整代码)
@Inject
MyRedisCache cache;
public Ranking getTopHeroes() {
return cache.getOrSetIfAbsent("top", () -> {
// Dumb approach, don't do this
return new Ranking(Hero.<Hero>listAll()
.stream()
.sorted((o1, o2) -> Integer.compare(o2.level, o1.level))
.peek(h -> {
// do something very long...
nap();
})
.limit(3)
.collect(Collectors.toList()));
});
}
要运行应用程序,只需启动 `mvn quarkus:dev` 并在浏览器中打开 https://:8080

要查看缓存的运行情况,请查看页面上显示的时间并刷新页面。请记住,缓存的值仅在 10 秒内有效(如 `MyRedisCache` 中设置的)。因此,如果您等待 10 秒,它将重新计算结果。
Quarkus 附带了一个 Redis 开发服务,它会在您的计算机上自动启动一个 Redis 实例并配置应用程序。请注意,您需要能够本地运行容器才能使用此功能。
结论
这篇博文简要介绍了新的 Redis API,并通过缓存实现示例演示了其用法。完整代码可在以下 GitHub 仓库 中找到。
该 API 支持更多功能,如地理空间数据、发布/订阅和事务,这些功能可用于改进 `getOrSetIfAbsent` 方法。我们将在未来的博文中介绍更高级的用例。
您可以在以下位置找到有关新 API 的更多详细信息:
此外,Quarkus 团队正在致力于将 Redis 作为缓存实现。因此,最终您只需使用 `@CacheResult` 即可实现自动缓存。