使用 Redis 响应式访问数据

本指南将引导您完成创建功能性响应式应用程序的过程,该应用程序使用 Spring Data 通过非阻塞 Lettuce 驱动程序与 Redis 进行交互。

您将构建什么

您将构建一个 Spring 应用程序,该应用程序使用 Spring Data RedisProject Reactor 响应式地与 Redis 数据存储交互,存储和检索 `Coffee` 对象而无需阻塞。此应用程序使用基于 Reactive Streams 规范的 Reactor 的 `Publisher` 实现,即 `Mono`(对于返回 0 或 1 个值的 Publisher)和 `Flux`(对于返回 0 到 n 个值的 Publisher)。

您需要什么

  • 大约 15 分钟

  • 您喜欢的文本编辑器或 IDE

  • Java 17 或更高版本

如何完成本指南

与大多数 Spring 入门指南 一样,您可以从头开始并完成每个步骤,也可以通过查看 此存储库 中的代码直接跳转到解决方案。

在本地环境中查看最终结果,您可以执行以下操作之一

设置 Redis 服务器

在构建消息应用程序之前,您需要设置服务器以处理接收和发送消息。本指南假设您使用 Spring Boot Docker Compose 支持。此方法的先决条件是您的开发机器具有可用的 Docker 环境,例如 Docker Desktop。添加依赖项 `spring-boot-docker-compose`,它执行以下操作:

  • 在您的工作目录中搜索 `compose.yml` 和其他常见的 compose 文件名。

  • 使用发现的 `compose.yml` 调用 `docker compose up`。

  • 为每个受支持的容器创建服务连接 Bean。

  • 应用程序关闭时调用 `docker compose stop`。

要使用 Docker Compose 支持,您只需遵循本指南即可。根据您引入的依赖项,Spring Boot 会找到正确的 `compose.yml` 文件,并在您运行应用程序时启动您的 Docker 容器。

如果您选择自己运行 Redis 服务器而不是使用 Spring Boot Docker Compose 支持,您可以选择以下几种方法:- 下载服务器 并手动运行它 - 如果您使用的是 Mac,则可以使用 Homebrew 安装 - 使用 `docker compose up` 手动运行 `compose.yaml` 文件。

如果您采用任何这些替代方法,则应从 Maven 或 Gradle 构建文件中删除 `spring-boot-docker-compose` 依赖项。您还需要向 `application.properties` 文件添加配置,这将在 准备构建应用程序 部分中更详细地介绍。如前所述,本指南假设您在 Spring Boot 中使用 Docker Compose 支持,因此此时不需要对 `application.properties` 进行额外更改。

从 Spring Initializr 开始

您可以使用此 预初始化项目 并单击“生成”以下载 ZIP 文件。此项目配置为适合本教程中的示例。

要手动初始化项目

  1. 导航到 https://start.spring.io。此服务会引入应用程序所需的所有依赖项,并为您完成大部分设置工作。

  2. 选择 Gradle 或 Maven 以及您要使用的语言。本指南假设您选择了 Java。

  3. 单击依赖项并选择Spring Reactive WebSpring Data Reactive RedisDocker Compose 支持

  4. 单击生成

  5. 下载生成的 ZIP 文件,它是使用您的选择配置的 Web 应用程序的存档。

如果您的 IDE 集成了 Spring Initializr,则可以从您的 IDE 中完成此过程。

创建域类

创建一个记录,表示我们希望在咖啡目录中库存的咖啡类型。

src/main/java/com/example/demo/Coffee.java

package com.example.demo;

public record Coffee(String id, String name) {
}

创建配置类

创建一个包含支持响应式 Redis 操作的 Spring Bean 的类。

src/main/java/com/example/demo/CoffeeConfiguration.java

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class CoffeeConfiguration {
  @Bean
  ReactiveRedisOperations<String, Coffee> redisOperations(ReactiveRedisConnectionFactory factory) {
    Jackson2JsonRedisSerializer<Coffee> serializer = new Jackson2JsonRedisSerializer<>(Coffee.class);

    RedisSerializationContext.RedisSerializationContextBuilder<String, Coffee> builder =
        RedisSerializationContext.newSerializationContext(new StringRedisSerializer());

    RedisSerializationContext<String, Coffee> context = builder.value(serializer).build();

    return new ReactiveRedisTemplate<>(factory, context);
  }

}

创建用于加载数据的 Spring Bean

创建一个 Spring Bean,以便在启动应用程序时加载应用程序的示例数据。

由于我们可能会多次(重新)启动应用程序,因此我们应该首先删除可能仍存在于先前执行中的任何数据。我们使用 `flushAll()`(Redis)服务器命令执行此操作。清除任何现有数据后,我们创建一个小的 `Flux`,将每个咖啡名称映射到 `Coffee` 对象,并将其保存到响应式 Redis 存储库。然后,我们查询存储库中的所有值并显示它们。

src/main/java/com/example/demo/CoffeeLoader.java

package com.example.demo;

import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;

import jakarta.annotation.PostConstruct;
import java.util.UUID;

@Component
public class CoffeeLoader {
  private final ReactiveRedisConnectionFactory factory;
  private final ReactiveRedisOperations<String, Coffee> coffeeOps;

  public CoffeeLoader(ReactiveRedisConnectionFactory factory, ReactiveRedisOperations<String, Coffee> coffeeOps) {
    this.factory = factory;
    this.coffeeOps = coffeeOps;
  }

  @PostConstruct
  public void loadData() {
    factory.getReactiveConnection().serverCommands().flushAll().thenMany(
        Flux.just("Jet Black Redis", "Darth Redis", "Black Alert Redis")
            .map(name -> new Coffee(UUID.randomUUID().toString(), name))
            .flatMap(coffee -> coffeeOps.opsForValue().set(coffee.id(), coffee)))
        .thenMany(coffeeOps.keys("*")
            .flatMap(coffeeOps.opsForValue()::get))
        .subscribe(System.out::println);
  }
}

创建 RestController

创建一个 `RestController` 为我们的应用程序提供外部接口。

src/main/java/com/example/demo/CoffeeController.java

package com.example.demo;

import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class CoffeeController {
  private final ReactiveRedisOperations<String, Coffee> coffeeOps;

  CoffeeController(ReactiveRedisOperations<String, Coffee> coffeeOps) {
    this.coffeeOps = coffeeOps;
  }

  @GetMapping("/coffees")
  public Flux<Coffee> all() {
    return coffeeOps.keys("*")
        .flatMap(coffeeOps.opsForValue()::get);
  }
}

运行应用程序

您可以通过 IDE 运行 main 方法。请注意,如果您已从解决方案存储库克隆了项目,您的 IDE 可能在错误的位置查找 `compose.yaml` 文件。您可以配置 IDE 以查找正确的位置,也可以使用命令行运行应用程序。`./gradlew bootRun` 和 `./mvnw spring-boot:run` 命令将启动应用程序并自动查找 compose.yaml 文件。

测试应用程序

应用程序运行后,从新的终端运行以下命令:

curl https://127.0.0.1:8080/coffees

您应该看到以下输出:

[
  {
    "id": "04ce0843-c9f8-40f6-942f-1ff643c1d426",
    "name": "Jet Black Redis"
  },
  {
    "id": "e2a0d798-5fa4-48a2-a45c-7770d8bb82bf",
    "name": "Black Alert Redis"
  },
  {
    "id": "13f13e3a-0798-44b7-8ae4-b319b227bb19",
    "name": "Darth Redis"
  }
]

准备构建应用程序

要运行不使用 Spring Boot Docker Compose 支持的代码,您需要本地运行的 Redis 版本才能连接。为此,您可以使用 Docker Compose,但必须首先对 `compose.yaml` 文件进行两处更改。首先,将 `compose.yaml` 中的 `ports` 条目修改为 `'6379:6379'`。其次,添加 `container_name`。

现在 `compose.yaml` 应该是:

services:
  redis:
    container_name: 'guide-redis'
    image: 'redis:latest'
    ports:
      - '6379:6379'

您现在可以运行 `docker compose up` 来启动 Redis 服务器。现在您应该有一个可以接受请求的外部 Redis 服务器。您可以重新运行应用程序,并使用您的外部 Redis 服务器查看相同的输出。

不需要在 `application.properties` 文件中进行任何配置,因为默认值与 `compose.yaml` 中的 Redis 服务器配置匹配。具体而言,属性 `spring.data.redis.host` 和 `spring.data.redis.port` 分别默认为 `localhost` 和 `6379`。

构建应用程序

本节介绍运行本指南的不同方法。

无论您选择如何运行应用程序,输出都应该相同。

要运行应用程序,您可以将应用程序打包为可执行 jar。`./mvnw clean package` 命令将应用程序编译为可执行 jar。然后,您可以使用 `java -jar target/demo-0.0.1-SNAPSHOT.jar` 命令运行 jar。

或者,如果您有可用的 Docker 环境,您可以使用 buildpacks 直接从您的 Maven 或 Gradle 插件创建 Docker 镜像。使用 Cloud Native Buildpacks,您可以创建可在任何地方运行的与 Docker 兼容的镜像。Spring Boot 直接包含 Maven 和 Gradle 的 buildpack 支持。这意味着您可以键入单个命令,并快速将合理的镜像放入本地运行的 Docker 守护程序中。要使用 Cloud Native Buildpacks 创建 Docker 镜像,请运行 `./mvnw spring-boot:build-image` 命令。启用 Docker 环境后,您可以使用 `docker run --network container:guide-redis docker.io/library/demo:0.0.1-SNAPSHOT` 命令运行应用程序。

`--network` 标志告诉 Docker 将我们的指南容器附加到我们的外部容器正在使用的现有网络。您可以在 Docker 文档 中找到更多信息。

原生镜像支持

Spring Boot 还支持 编译为原生镜像,前提是您的机器上有 GraalVM 发行版。

然后,您可以运行 `./mvnw -Pnative native:compile` 命令生成原生镜像。构建完成后,您可以通过执行 `target/demo` 命令来运行代码,并获得几乎即时的启动时间。

要使用 Maven 创建原生镜像容器,您应该确保您的 `pom.xml` 文件使用 `spring-boot-starter-parent` 和 `org.graalvm.buildtools:native-maven-plugin`。此插件应位于 ` ` 部分中。

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
</plugin>

您还可以使用 Buildpacks 创建原生镜像。您可以通过运行 `./mvnw -Pnative spring-boot:build-image` 命令生成原生镜像。构建完成后,您可以使用 `docker run --network container:guide-redis docker.io/library/demo:0.0.1-SNAPSHOT` 命令启动应用程序。

在 Docker 中测试应用程序

如果您使用 Docker 指令(如前所示)运行了应用程序,则来自终端或命令行的简单 curl 命令将不再有效。这是因为我们在 Docker 网络 中运行容器,该网络无法从终端或命令行访问。要运行 curl 命令,我们可以启动第三个容器来运行我们的 curl 命令并将其附加到同一个网络。

首先,获取一个交互式 shell 到一个新的容器,该容器在与 Redis 容器和应用程序相同的网络上运行。

docker run --rm --network container:guide-redis -it alpine

接下来,从容器内的 shell 中安装 curl。

apk add curl

最后,您可以运行 测试应用程序 中所述的 curl 命令。

总结

恭喜!您已经开发了一个 Spring 应用程序,该应用程序使用 Spring Data 和 Redis 进行完全响应式、非阻塞的数据库访问!

想编写新的指南或为现有指南做出贡献?请查看我们的 贡献指南

所有指南均使用 ASLv2 许可证发布代码,并使用 署名-非衍生作品创作共用许可证 发布编写内容。

获取代码