Getting Started With Reactive

_Reactive_是一组构建强大、高效且并行应用程序和系统的原则。这些原则允许您处理比传统方法更多负载,同时更有效地使用资源(CPU 和内存),同时按部就班地处理故障。 Quarkus 是一款 _Reactive_框架。从一开始,_Reactive_就一直是 Quarkus 架构的一项重要原则。它包括许多反应式特性并提供了广泛的生态系统。 本指南并非关于什么是 _Reactive_以及 Quarkus 如何启用反应式架构的深入文章。如果您希望了解更多有关这些主题的信息,请参阅 Reactive Architecture guide,其中提供了 Quarkus 反应式生态系统的概述。 在本指南中,我们将带您入门 Quarkus 的一些反应式特性。我们将实现一个简单的 CRUD 应用程序。然而,与 Hibernate with Panache guide不同,它使用 Quarkus 的反应式特性。 本指南将帮助您:

  • 使用 Quarkus 自举反应式 CRUD 应用程序

  • 使用带有 Panache 的 Hibernate Reactive 以反应式方式与数据库交互

  • 使用 Quarkus REST(以前的 RESTEasy Reactive)来实现 HTTP API,同时执行反应式原则

  • 打包和运行该应用程序

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

验证 Maven 是否正在使用您期望的 Java 版本。如果您安装了多个 JDK,请确保 Maven 正在使用预期的 JDK。您可以通过运行 `mvn --version.`验证 Maven 使用的 JDK

Imperative vs. Reactive: a question of threads

如上所述,在本指南中,我们将实现一个反应式 CRUD 应用程序。但您可能想知道它与传统命令模型相比有何不同和优势。

为更好地理解这种对比,我们需要解释反应式和命令执行模型之间的差异。理解 _Reactive_不仅仅是不同的执行模型至关重要,但为了理解本指南,这种区分非常必要。

在传统的命令方法中,框架分配了一个线程来处理请求。因此,请求的整个处理都在此工作线程上运行。该模型扩展性不是很强。事实上,要处理多个并发请求,您需要多个线程;因此,您应用程序的并发性受到线程数量的限制。此外,代码一旦与远程服务交互,这些线程就会被阻塞。因此,这会导致低效地使用资源,因为您可能需要更多线程,而每个线程(由于它们被映射到 OS 线程)在内存和 CPU 方面都有成本。

blocking threads

另一方面,反应式模型依赖于非阻塞 I/O 和不同的执行模型。非阻塞 I/O 提供了一种处理并发 I/O 的有效方法。称为 I/O 线程的线程量极小,可以处理许多并发 I/O。使用这种模型,请求处理不会委托给工作线程,而是直接使用这些 I/O 线程。由于不需要创建工作线程来处理请求,因此节省了内存和 CPU。它还提高了并发性,因为它消除了线程数量的限制。最后,它还缩短了响应时间,因为它减少了线程切换的数量。

reactive thread

From sequential to continuation style

因此,在反应式执行模型中,请求是使用 I/O 线程处理的。但事实并非如此。I/O 线程可以处理多个并发请求。该怎么做呢?这是反应式和命令式之间的技巧和最显着的差异之一。

在处理请求涉及与远程服务互动时,例如 HTTP API 或数据库,它不会在等待响应时阻塞执行。相反,它调度 I/O 操作并附加一个延续操作,即处于请求处理中的代码。此延续操作可以通过回调传递(使用 I/O 结果调用的函数),或使用更高级的构造,如反应式编程或协程。无论如何表达延续操作,释放 I/O 线程都是一个关键方面,随后这个线程可用于处理其他请求。在计划的 I/O 完成后,I/O 线程将执行延续操作,并且继续处理暂挂的请求。

因此,与 I/O 阻塞执行命令式模型不同,反应式模型切换到基于延续操作的设计,其中会释放 I/O 线程,并且在 I/O 完成时调用延续操作。因此,I/O 线程可处理多个并发请求,改善应用程序的整体并发性。

但是,有一个问题。我们需要一个方法来编写延续传递代码。有许多方式可用来实现此目的。在 Quarkus 中,我们提出:

  • Mutiny - 一个直观且事件驱动的反应式编程库

  • Kotlin 协程 - 以顺序方式编写异步代码的方法

在该指南中我们将使用 Mutiny。如需了解有关 Mutiny 的更多信息,请查看 Mutiny documentation

Project Loom 将很快进入 JDK 中并提出了基于虚拟线程的模型。只要 Loom 普遍可用,Quarkus 架构就准备好支持它。

Bootstrapping the Reactive Fruits application

有了这一点,我们来看看如何使用 Quarkus 开发 CRUD 应用程序,它将使用 I/O 线程来处理 HTTP 请求,与数据库互动,处理结果,并且编写 HTTP 响应;换句话说:反应式 CRUD 应用程序。

首先,转到 code.quarkus.io并选择以下扩展:

  1. Quarkus REST Jackson

  2. Hibernate Reactive with Panache

  3. Reactive PostgreSQL client

reactive guide code

该最后一个扩展是适用于 PostgreSQL 的反应式数据库驱动程序。Hibernate Reactive 使用该驱动程序,与数据库互动时不阻塞呼叫方线程。

在选中之后,点击“生成您的应用程序”,下载该 zip 文件,解压缩它,并且在您最喜爱的 IDE 中打开代码。

Reactive Panache Entity

让我们从 `Fruit`实体开始。使用以下内容创建 `src/main/java/org/acme/hibernate/orm/panache/Fruit.java`文件:

package org.acme.hibernate.orm.panache;

import jakarta.persistence.Cacheable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;

import io.quarkus.hibernate.reactive.panache.PanacheEntity;  (1)

@Entity
@Cacheable
public class Fruit extends PanacheEntity {

	 @Column(length = 40, unique = true)
	 public String name;

}
1 请确保您导入 `PanacheEntity`的反应式变体。

该类表示 Fruits。它是一个具有单字段 (name) 的简单实体。请注意,它使用 io.quarkus.hibernate.reactive.panache.PanacheEntity,该变体是 `PanacheEntity`的反应式变体。因此,在幕后,Hibernate 使用我们上面所述的执行模型。它与数据库互动时不阻塞该线程。此外,该反应式 `PanacheEntity`提出了一个反应式 API。我们将使用此 API 来实现 REST 端点。

在更深入地了解之前,打开 `src/main/resources/application.properties`文件并添加:

quarkus.datasource.db-kind=postgresql
quarkus.hibernate-orm.database.generation=drop-and-create

它指示应用程序针对数据库使用 PostgreSQL,并且处理数据库架构生成。

在同一目录中,创建一个 `import.sql`文件,它插入一些水果,所以我们在 dev 模式中不从一个空的数据库开始:

INSERT INTO fruit(id, name) VALUES (1, 'Cherry');
INSERT INTO fruit(id, name) VALUES (2, 'Apple');
INSERT INTO fruit(id, name) VALUES (3, 'Banana');
ALTER SEQUENCE fruit_seq RESTART WITH 4;

在终端中,使用 `./mvnw quarkus:dev`以 dev 模式启动应用程序。Quarkus 会自动为您启动一个数据库实例并配置应用程序。现在我们只需要实现 HTTP 端点。

Reactive Resource

由于与数据库的交互是非阻塞和异步的,我们需要使用异步构造来实现 HTTP 资源。Quarkus 采用 Mutiny 作为其核心响应式编程模型。因此,它支持从 HTTP 端点返回 Mutiny 类型(@“1”和 @“2”)。此外,我们的 Fruit Panache 实体公开了使用这些类型的函数,因此我们只需要实现 @“3”。

使用下列内容创建 @“4” 文件:

package org.acme.hibernate.orm.panache;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.Path;

@Path("/fruits")
@ApplicationScoped
public class FruitResource {

}

让我们从 @“5” 方法开始。@“6” 方法返回存储在数据库的所有水果。在 @“7” 中,添加下列代码:

@GET
public Uni<List<Fruit>> get() {
    return Fruit.listAll(Sort.by("name"));
}

打开 [role="bare"]@“8” 以调用此方法:

[{"id":2,"name":"Apple"},{"id":3,"name":"Banana"},{"id":1,"name":"Cherry"},{"id":4,"name":"peach"}]

我们得到了预期的 JSON 数组。除非另有指示,否则 Quarkus REST 会自动将该列表映射到 JSON 数组。

查看返回类型,它返回一个 @“10” 的 @“9” 。@“11” 是一个异步类型。它有点像 future。它是一个占位符,将在稍后获取其值(项目)。当它接收项目(Mutiny 指出它 @“12” 其项目)时,你可以附加一些行为。这就是我们表达延续的方式:获取一个 uni,当该 uni 发射其项目时,执行处理的其余部分。

响应式开发人员可能会疑惑为什么我们不能直接返回水果流。在与数据库打交道时,这往往不是一个好主意。关系数据库不能很好地处理流。这是不针对此用例设计的协议的一个问题。因此,要从数据库流式传输行,你需要保持连接(有时还有事务)处于开启状态,直到所有行都被使用。如果你有慢速使用者,你将破坏数据库的黄金法则:不要长时间持有连接。事实上,连接数相当少,而让使用者长时间持有它们,会大大降低应用程序的并发性。因此,尽可能使用 @“13” 并加载内容。如果你有一大组结果,请实现分页。

让我们使用 @“14” 继续我们的 API:

@GET
@Path("/{id}")
public Uni<Fruit> getSingle(Long id) {
    return Fruit.findById(id);
}

在此情况下,我们使用 @“15” 检索水果。它将返回一个 @“16”,该 @“16” 将在数据库检索行后完成。

@“17” 方法允许向数据库添加一个新水果:

@POST
public Uni<RestResponse<Fruit>> create(Fruit fruit) {
    return Panache.withTransaction(fruit::persist).replaceWith(RestResponse.status(CREATED, fruit));
}

该代码涉及更多内容。要写入数据库,我们需要一个事务,因此我们使用 @“18” 获取一个(异步)并继续调用 @“19” 方法。@“20” 方法返回一个 @“21”,它发出水果在数据库中插入结果。一旦插入完成(它充当延续的角色),我们创建 @“22” HTTP 响应。

如果你的计算机上装有 @“23”,你可以使用以下命令尝试端点:

> curl --header "Content-Type: application/json" \
  --request POST \
  --data '{"name":"peach"}' \
  http://localhost:8080/fruits

遵循同样的思路,你可以实现其他 CRUD 方法。

Testing and Running

测试响应式应用程序类似于测试非响应式应用程序:使用 HTTP 端点并验证 HTTP 响应。应用程序是否响应式这一事实并没有改变任何内容。

在 @“24” 中,你可以查看如何实现水果应用程序的测试。

应用程序的打包和运行也不会改变。

你可以像往常一样使用以下命令:

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

或构建一个本机可执行文件:

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

你也可以将应用程序打包成容器。

要运行应用程序,请务必启动一个数据库并向应用程序提供配置。

例如,您可以使用 Docker 运行您的数据库:

docker run -it --rm=true \
    --name postgres-quarkus -e POSTGRES_USER=quarkus \
    -e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=fruits \
    -p 5432:5432 postgres:14.1

然后,使用以下内容启动应用程序:

java \
   -Dquarkus.datasource.reactive.url=postgresql://localhost/fruits \
   -Dquarkus.datasource.username=quarkus \
   -Dquarkus.datasource.password=quarkus \
   -jar target/quarkus-app/quarkus-run.jar

或者,如果您将应用程序打包为本地可执行文件,请使用:

./target/getting-started-with-reactive-runner \
  -Dquarkus.datasource.reactive.url=postgresql://localhost/fruits \
  -Dquarkus.datasource.username=quarkus \
  -Dquarkus.datasource.password=quarkus

传递给应用程序的参数已在数据源指南中进行了解释。还有其他方法可用于配置应用程序 - 请查看 configuration guide 以了解各种可能性(例如环境变量、.env 文件等)。

Going further

本指南简要介绍了 Quarkus 提供的一些反应式功能。Quarkus 是一个反应式框架,因此提供了许多反应式功能。

如果您想继续了解此主题,请查看: