Using the Redis Client

本指南演示了 Quarkus 应用程序如何使用 Redis 客户端扩展连接到 Redis 服务器。 :iokays-category: quarkus :iokays-path: modules/ROOT/pages/_includes/extension-status.adoc :keywords: Quarkus, 中文文档, 编程技术

该技术被认为是 {extension-status}。 有关可能状态的完整列表,请查看我们的 FAQ entry.

Prerequisites

include::./_includes/prerequisites.adoc[]* 正在运行的 Docker 环境

Architecture

在本指南中,我们将公开一个简单的 Rest API 来使用 INCRBY 命令递增数字。同时,我们将了解如何使用其他 Redis 命令,如 GETSET(来自字符串组)、DELKEYS(来自键组)。

我们将使用 Quarkus Redis 扩展来连接和与 Redis 交互。

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

解决方案位于 redis-quickstart directory 中。

Creating the Maven Project

首先,我们需要一个新项目。使用以下命令创建一个新项目:

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

此命令会生成一个新项目,导入 Redis 扩展。

如果您已经配置了您的 Quarkus 项目,则可以通过在项目的根目录中运行以下命令将 @"1" 扩展添加到您的项目:

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

这会将以下内容添加到构建文件中:

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

Creating the Increment POJO

我们将使用 @"2" POJO 来建模我们的增量。创建 @"3" 文件,内容如下:

package org.acme.redis;

public class Increment {
    public String key; (1)
    public long value; (2)

    public Increment(String key, long value) {
        this.key = key;
        this.value = value;
    }

    public Increment() {
    }
}
1 用作 Redis 密钥的密钥
2 Redis 密钥保存的值

Creating the Increment Service

我们将创建一个 @"4" 类,其将发挥 Redis客户端的作用。有了这个类,我们将能够执行 @"5"、@"6"、@"7"、@"8" 和 @"9" Redis 命令。

创建 @"10" 文件,内容如下:

package org.acme.redis;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import io.quarkus.redis.datasource.ReactiveRedisDataSource;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.keys.KeyCommands;
import io.quarkus.redis.datasource.keys.ReactiveKeyCommands;
import io.quarkus.redis.datasource.string.StringCommands;
import io.smallrye.mutiny.Uni;

@ApplicationScoped
public class IncrementService {

    // This quickstart demonstrates both the imperative
    // and reactive Redis data sources
    // Regular applications will pick one of them.

    private ReactiveKeyCommands<String> keyCommands; (1)
    private ValueCommands<String, Long> countCommands; (2)

    public IncrementService(RedisDataSource ds, ReactiveRedisDataSource reactive) { (3)
        countCommands = ds.value(Long.class); (4)
        keyCommands = reactive.key();  (5)

    }


    long get(String key) {
        Long value = countCommands.get(key); (6)
        if (value == null) {
            return 0L;
        }
        return value;
    }

    void set(String key, Long value) {
        countCommands.set(key, value); (7)
    }

    void increment(String key, Long incrementBy) {
        countCommands.incrby(key, incrementBy); (8)
    }

    Uni<Void> del(String key) {
        return keyCommands.del(key) (9)
            .replaceWithVoid();
    }

    Uni<List<String>> keys() {
        return keyCommands.keys("*"); (10)
    }
}
1 用于操作密钥的字段
2 用于操作计数器的字段
3 注入命令式和响应式数据源
4 检索用于操作计数器的命令
5 检索用于操作密钥的命令
6 检索与给定密钥相关的值。 如果 @"11",则返回 0。
7 设置与给定密钥相关的值
8 递增与给定密钥相关的值
9 删除密钥(及其相关值)
10 List all the keys

Creating the Increment Resource

创建 @"12" 文件,内容如下:

package org.acme.redis;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.DELETE;
import java.util.List;

import io.smallrye.mutiny.Uni;

@Path("/increments")
public class IncrementResource {

    @Inject
    IncrementService service;

    @GET
    public Uni<List<String>> keys() {
        return service.keys();
    }

    @POST
    public Increment create(Increment increment) {
        service.set(increment.key, increment.value);
        return increment;
    }

    @GET
    @Path("/{key}")
    public Increment get(String key) {
        return new Increment(key, service.get(key));
    }

    @PUT
    @Path("/{key}")
    public void increment(String key, long value) {
        service.increment(key, value);
    }

    @DELETE
    @Path("/{key}")
    public Uni<Void> delete(String key) {
        return service.del(key);
    }
}

Creating the test class

编辑 pom.xml 文件以添加以下依赖项:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

创建 @"13" 文件,内容如下:

package org.acme.redis;

import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;

import static io.restassured.RestAssured.given;

import io.restassured.http.ContentType;

@QuarkusTest
public class IncrementResourceTest {

    @Test
    public void testRedisOperations() {
        // verify that we have nothing
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments")
                .then()
                .statusCode(200)
                .body("size()", is(0));

        // create a first increment key with an initial value of 0
        given()
                .contentType(ContentType.JSON)
                .accept(ContentType.JSON)
                .body("{\"key\":\"first-key\",\"value\":0}")
                .when()
                .post("/increments")
                .then()
                .statusCode(200)
                .body("key", is("first-key"))
                .body("value", is(0));

        // create a second increment key with an initial value of 10
        given()
                .contentType(ContentType.JSON)
                .accept(ContentType.JSON)
                .body("{\"key\":\"second-key\",\"value\":10}")
                .when()
                .post("/increments")
                .then()
                .statusCode(200)
                .body("key", is("second-key"))
                .body("value", is(10));

        // increment first key by 1
        given()
                .contentType(ContentType.JSON)
                .body("1")
                .when()
                .put("/increments/first-key")
                .then()
                .statusCode(204);

        // verify that key has been incremented
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments/first-key")
                .then()
                .statusCode(200)
                .body("key", is("first-key"))
                .body("value", is(1));

        // increment second key by 1000
        given()
                .contentType(ContentType.JSON)
                .body("1000")
                .when()
                .put("/increments/second-key")
                .then()
                .statusCode(204);

        // verify that key has been incremented
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments/second-key")
                .then()
                .statusCode(200)
                .body("key", is("second-key"))
                .body("value", is(1010));

        // verify that we have two keys in registered
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments")
                .then()
                .statusCode(200)
                .body("size()", is(2));

        // delete first key
        given()
                .accept(ContentType.JSON)
                .when()
                .delete("/increments/first-key")
                .then()
                .statusCode(204);

        // verify that we have one key left after deletion
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments")
                .then()
                .statusCode(200)
                .body("size()", is(1));

        // delete second key
        given()
                .accept(ContentType.JSON)
                .when()
                .delete("/increments/second-key")
                .then()
                .statusCode(204);

        // verify that there is no key left
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments")
                .then()
                .statusCode(200)
                .body("size()", is(0));
    }
}

Get it running

如果您按照说明进行操作,则您应该已运行 Redis 服务器。 然后,您只需使用以下命令运行该应用程序:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

打开另一个终端并运行 @"14" 命令。

Interacting with the application

正如我们在上面看到的,API 暴露了五个 Rest 端点。在本部分中,我们将了解如何初始化自增值、查看当前自增值列表、增量给定键的值、检索自增值的当前值,最后删除键。

Creating a new increment

curl -X POST -H "Content-Type: application/json" -d '{"key":"first","value":10}' http://localhost:8080/increments 1
1 我们用键 `first`和初始值 `10`创建第一个自增值。

运行以上命令应返回以下结果:

{
  "key": "first",
  "value": 10
}

See current increments keys

若要查看当前自增值键列表,请运行以下命令:

curl http://localhost:8080/increments

以上命令应该返回 ["first"],表明到目前为止,我们只有一个自增值。

Retrieve a new increment

若要使用其键检索一个自增值,我们将不得不运行以下命令:

curl http://localhost:8080/increments/first 1
1 运行此命令,应返回以下结果:
{
  "key": "first",
  "value": 10
}

Increment a value given its key

若要增量一个值,请运行以下命令:

curl -X PUT -H "Content-Type: application/json" -d '27' http://localhost:8080/increments/first 1
1 将 `first`的值增加 27。

现在,运行命令 `curl [role="bare"]http://localhost:8080/increments/first`应返回以下结果:

{
  "key": "first",
  "value": 37 1
}
1 我们看到 first`键的值现在是 `37,这正是 10 + 27 的结果,快速数学。

Deleting a key

使用以下命令,根据其键删除一个自增值。

curl -X DELETE  http://localhost:8080/increments/first 1
1 Delete the first increment.

现在,运行命令 curl [role="bare"]http://localhost:8080/increments`应返回一个空列表 `[]

Configuring for production

此时,Quarkus 使用 Redis Dev Service 来运行 Redis 服务器并配置应用程序。然而,在实际生产环境中,您将运行自己的 Redis(或使用云服务)。

让我们使用以下命令在端口 6379 上启动一个 Redis 服务器:

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name redis_quarkus_test -p 6379:6379 redis:5.0.6

然后,打开 src/main/resources/application.properties 文件并添加:

%prod.quarkus.redis.hosts=redis://localhost:6379

Packaging and running in JVM mode

您可以将应用程序作为常规 jar 文件运行。

首先,我们需要对其进行打包:

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

此命令将启动一个 Redis 实例来执行测试。

然后运行它:

java -jar target/quarkus-app/quarkus-run.jar

Running Native

您还可以从此应用程序创建本机可执行程序,而无需进行任何源代码更改。本机可执行程序消除了对 JVM 的依赖:在本机可执行程序中包含了在目标平台上运行应用程序所需的一切,从而允许应用程序以最小的资源开销运行。

编译本机可执行程序需要花费更长时间,因为 GraalVM 会执行额外的步骤来删除不必要的代码路径。使用 native 配置文件来编译本机可执行程序:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

构建完成后,您可以使用以下命令运行可执行程序:

./target/redis-quickstart-1.0.0-SNAPSHOT-runner

Going further

若要了解有关 Quarkus Redis 扩展的更多信息,请查看 the Redis extension reference guide