介绍新的 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` 即可实现自动缓存。