Using the Redis Client

本指南演示了 Quarkus 应用程序如何使用 Redis 客户端扩展连接到 Redis 服务器。

This guide demonstrates how your Quarkus application can connect to a Redis server using the Redis Client extension. Unresolved directive in redis.adoc - include::{includes}/extension-status.adoc[]

Prerequisites

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

Unresolved directive in redis.adoc - include::{includes}/prerequisites.adoc[] * A working Docker environment

Architecture

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

In this guide, we are going to expose a simple Rest API to increment numbers by using the INCRBY command. Along the way, we’ll see how to use other Redis commands like GET, SET (from the string group), DEL and KEYS (from the key group).

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

We’ll be using the Quarkus Redis extension to connect to interact with Redis.

Solution

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

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

Clone the Git repository: git clone {quickstarts-clone-url}, or download an {quickstarts-archive-url}[archive].

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

The solution is located in the redis-quickstart directory.

Creating the Maven Project

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

First, we need a new project. Create a new project with the following command:

Unresolved directive in redis.adoc - include::{includes}/devtools/create-app.adoc[]

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

This command generates a new project, importing the Redis extension.

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

If you already have your Quarkus project configured, you can add the redis-client extension to your project by running the following command in your project base directory:

Unresolved directive in redis.adoc - include::{includes}/devtools/extension-add.adoc[]

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

This will add the following to your build file:

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" 文件,内容如下:

We are going to model our increments using the Increment POJO. Create the src/main/java/org/acme/redis/Increment.java file, with the following content:

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 The key that will be used as the Redis key
2 The value held by the Redis key

Creating the Increment Service

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

We are going to create an IncrementService class which will play the role of a Redis client. With this class, we’ll be able to perform the SET, GET , DEL, KEYS and INCRBY Redis commands.

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

Create the src/main/java/org/acme/redis/IncrementService.java file, with the following content:

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 The field use to manipulate keys
2 The field use to manipulate the counter
3 Inject both the imperative and reactive data sources
4 Retrieve the commands to manipulate the counters
5 Retrieve the commands to manipulate the keys
6 Retrieve the value associated with the given key. It null, returns 0.
7 Set the value associated with the given key
8 Increment the value associated with the given key
9 Delete a key (and its associated value)
10 List all the keys

Creating the Increment Resource

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

Create the src/main/java/org/acme/redis/IncrementResource.java file, with the following content:

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 文件以添加以下依赖项:

Edit the pom.xml file to add the following dependency:

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

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

Create the src/test/java/org/acme/redis/IncrementResourceTest.java file with the following content:

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 服务器。 然后,您只需使用以下命令运行该应用程序:

If you followed the instructions, you should have the Redis server running. Then, you just need to run the application using:

Unresolved directive in redis.adoc - include::{includes}/devtools/dev.adoc[]

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

Open another terminal and run the curl [role="bare"]http://localhost:8080/increments command.

Interacting with the application

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

As we have seen above, the API exposes five Rest endpoints. In this section we are going to see how to initialise an increment, see the list of current increments, incrementing a value given its key, retrieving the current value of an increment, and finally deleting a key.

Creating a new increment

curl -X POST -H "Content-Type: application/json" -d '{"key":"first","value":10}' http://localhost:8080/increments 1
1 We create the first increment, with the key first and an initial value of 10.

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

Running the above command should return the result below:

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

See current increments keys

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

To see the list of current increments keys, run the following command:

curl http://localhost:8080/increments

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

The above command should return ["first"] indicating that we have only one increment thus far.

Retrieve a new increment

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

To retrieve an increment using its key, we will have to run the below command:

curl http://localhost:8080/increments/first 1
1 Running this command, should return the following result:
{
  "key": "first",
  "value": 10
}

Increment a value given its key

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

To increment a value, run the following command:

curl -X PUT -H "Content-Type: application/json" -d '27' http://localhost:8080/increments/first 1
1 Increment the first value by 27.

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

Now, running the command curl [role="bare"]http://localhost:8080/increments/first should return the following result:

{
  "key": "first",
  "value": 37 1
}
1 We see that the value of the first key is now 37 which is exactly the result of 10 + 27, quick maths.

Deleting a key

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

Use the command below, to delete an increment given its key.

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

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

Now, running the command curl [role="bare"]http://localhost:8080/increments should return an empty list []

Configuring for production

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

At this point, Quarkus uses the Redis Dev Service to run a Redis server and configure the application. However, in production, you will run your own Redis (or used a Cloud offering).

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

Let’s start a Redis server on the port 6379 using:

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 文件并添加:

Then, open the src/main/resources/application.properties file and add:

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

Packaging and running in JVM mode

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

You can run the application as a conventional jar file.

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

First, we will need to package it:

Unresolved directive in redis.adoc - include::{includes}/devtools/build.adoc[]

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

This command will start a Redis instance to execute the tests.

然后运行它:

Then run it:

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

Running Native

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

You can also create a native executable from this application without making any source code changes. A native executable removes the dependency on the JVM: everything needed to run the application on the target platform is included in the executable, allowing the application to run with minimal resource overhead.

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

Compiling a native executable takes a bit longer, as GraalVM performs additional steps to remove unnecessary codepaths. Use the native profile to compile a native executable:

Unresolved directive in redis.adoc - include::{includes}/devtools/build-native.adoc[]

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

Once the build is finished, you can run the executable with:

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

Going further

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

To learn more about the Quarkus Redis extension, check the Redis extension reference guide.