使用 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 开始

你可以使用这个 预配置项目并点击 Generate 下载 ZIP 文件。此项目已配置好以适合本教程中的示例。

手动初始化项目

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

  2. 选择 Gradle 或 Maven 以及你想使用的语言。本指南假定你选择了 Java。

  3. 点击 Dependencies 并选择 Spring Reactive WebSpring Data Reactive RedisDocker Compose Support

  4. 点击 Generate

  5. 下载生成的 ZIP 文件,它是一个包含你所选配置的 web 应用程序存档。

如果你的 IDE 集成了 Spring Initializr,你可以直接从 IDE 中完成此过程。

创建域类

创建一个 record,代表我们希望在咖啡目录中存入的一种咖啡

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

package com.example.demo;

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

创建配置类

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

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 http://localhost: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.hostspring.data.redis.port 分别默认为 localhost6379

构建应用程序

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

无论你选择哪种方式运行应用程序,输出应该都是相同的。

要运行应用程序,可以将其打包为可执行的 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 daemon 中。要使用 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-parentorg.graalvm.buildtools:native-maven-plugin。此插件应位于 <build> <plugins> 部分中

<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 许可发布,写作内容以 署名-禁止演绎知识共享许可 发布。

获取代码