Reactive 入门
响应式是一套构建健壮、高效和并发应用程序和系统的原则。这些原则让您能够处理比传统方法更多的负载,同时更有效地使用资源(CPU 和内存),并优雅地应对故障。
Quarkus 是一个 响应式 框架。从一开始,响应式一直是 Quarkus 架构的重要原则。它包含许多响应式特性,并提供广泛的生态系统。
本指南不是一篇深入探讨 响应式 是什么以及 Quarkus 如何启用响应式架构的文章。如果您想阅读更多关于这些主题的信息,请参阅 响应式架构指南,该指南提供了 Quarkus 响应式生态系统的概述。
在本指南中,我们将帮助您开始使用 Quarkus 的一些响应式特性。我们将实现一个简单的 CRUD 应用程序。但是,与 带有 Panache 的 Hibernate 指南 不同,它使用了 Quarkus 的响应式特性。
本指南将帮助您
-
使用 Quarkus 引导一个响应式 CRUD 应用程序
-
使用带有 Panache 的 Hibernate Reactive 以响应式方式与数据库交互
-
使用 Quarkus REST(以前的 RESTEasy Reactive)实现 HTTP API,同时强制执行响应式原则
-
打包和运行应用程序
先决条件
要完成本指南,您需要
-
大约 15 分钟
-
一个 IDE
-
已安装 JDK 17+ 并正确配置了
JAVA_HOME
-
Apache Maven 3.9.9
-
如果您想使用它,可以选择 Quarkus CLI
-
如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置
验证 Maven 是否正在使用您期望的 Java 版本。如果您安装了多个 JDK,请确保 Maven 使用的是预期的 JDK。您可以通过运行 mvn --version 来验证 Maven 使用哪个 JDK。 |
命令式 vs. 响应式:线程问题
如上所述,在本指南中,我们将实现一个响应式 CRUD 应用程序。但您可能想知道与传统和命令式模型相比,区别和好处是什么。
为了更好地理解这种对比,我们需要解释响应式和命令式执行模型之间的区别。重要的是要理解 响应式 不仅仅是一种不同的执行模型,但这种区别对于理解本指南是必要的。
在传统和命令式方法中,框架会分配一个线程来处理请求。因此,请求的整个处理都在这个工作线程上运行。此模型的可伸缩性不是很好。实际上,要处理多个并发请求,您需要多个线程;因此,您的应用程序并发性受到线程数的限制。此外,一旦您的代码与远程服务交互,这些线程就会被阻塞。因此,这会导致资源利用率低下,因为您可能需要更多线程,并且每个线程(因为它们映射到操作系统线程)在内存和 CPU 方面都有成本。

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

从顺序样式到延续样式
因此,使用响应式执行模型,使用 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 文档。
Project Loom 即将进入 JDK,并提出了一种基于虚拟线程的模型。Quarkus 架构已准备好在 Loom 全局可用时立即支持它。 |
引导响应式 Fruits 应用程序
考虑到这一点,让我们看看如何使用 Quarkus 开发一个 CRUD 应用程序,该应用程序将使用 I/O 线程来处理 HTTP 请求、与数据库交互、处理结果和写入 HTTP 响应;换句话说:一个响应式 CRUD 应用程序。
虽然我们建议您按照分步说明进行操作,但您可以在 https://github.com/quarkusio/quarkus-quickstarts/tree/main/hibernate-reactive-panache-quickstart 上找到最终解决方案。
首先,转到 code.quarkus.io 并选择以下扩展
-
Quarkus REST Jackson
-
带有 Panache 的 Hibernate Reactive
-
Reactive PostgreSQL 客户端

最后一个扩展是 PostgreSQL 的响应式数据库驱动程序。Hibernate Reactive 使用该驱动程序与数据库交互,而不会阻塞调用线程。
选择后,单击“生成您的应用程序”,下载 zip 文件,将其解压缩,然后在您喜欢的 IDE 中打开代码。
响应式 Panache 实体
让我们从 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.schema-management.strategy=drop-and-create
它指示应用程序使用 PostgreSQL 作为数据库,并处理数据库架构生成。
在同一目录下,创建一个 import.sql
文件,该文件插入一些水果,因此我们不会在开发模式下从空数据库开始
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
。Quarkus 会自动为您启动数据库实例并配置应用程序。现在我们只需要实现 HTTP 端点。
响应式资源
因为与数据库的交互是非阻塞和异步的,所以我们需要使用异步构造来实现我们的 HTTP 资源。Quarkus 使用 Mutiny 作为其核心响应式编程模型。因此,它支持从 HTTP 端点返回 Mutiny 类型(Uni
和 Multi
)。此外,我们的 Fruit Panache 实体公开了使用这些类型的方法,因此我们只需要实现 胶水 。
创建 src/main/java/org/acme/hibernate/orm/panache/FruitResource.java
文件,内容如下
package org.acme.hibernate.orm.panache;
import java.util.List;
import io.quarkus.panache.common.Sort;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.Path;
@Path("/fruits")
@ApplicationScoped
public class FruitResource {
}
让我们从 getAll
方法开始。getAll
方法返回数据库中存储的所有水果。在 FruitResource
中,添加以下代码
@GET
public Uni<List<Fruit>> get() {
return Fruit.listAll(Sort.by("name"));
}
打开 https://:8080/fruits 以调用此方法
[{"id":2,"name":"Apple"},{"id":3,"name":"Banana"},{"id":1,"name":"Cherry"},{"id":4,"name":"peach"}]
我们得到了预期的 JSON 数组。除非另有说明,否则 Quarkus REST 会自动将列表映射到 JSON 数组。
查看返回类型;它返回 List<Fruit>
的 Uni
。Uni
是一种异步类型。它有点像 future。它是一个占位符,稍后会得到它的值(项)。当它收到该项(Mutiny 说它 发出 它的项)时,您可以附加一些行为。这就是我们表达延续的方式:获取一个 uni,当 uni 发出它的项时,执行其余的处理。
响应式开发人员可能想知道为什么我们不能直接返回水果流。在处理数据库时,这往往是一个坏主意。关系数据库不能很好地处理流式传输。这是一个协议问题,不是为此用例设计的。因此,要从数据库中流式传输行,您需要保持连接(有时是事务)打开,直到所有行都被使用。如果您有慢速消费者,您将打破数据库的黄金法则:不要长时间保持连接。实际上,连接数相当低,让消费者长时间保持连接会大大降低应用程序的并发性。因此,如果可能,请使用 Uni<List<T>> 并加载内容。如果您有大量结果,请实现分页。 |
让我们继续我们的 API,使用 getSingle
@GET
@Path("/{id}")
public Uni<Fruit> getSingle(Long id) {
return Fruit.findById(id);
}
在这种情况下,我们使用 Fruit.findById
来检索水果。它返回一个 Uni
,当数据库检索到该行时,它将完成。
create
方法允许向数据库添加新水果
@POST
public Uni<RestResponse<Fruit>> create(Fruit fruit) {
return Panache.withTransaction(fruit::persist).replaceWith(RestResponse.status(CREATED, fruit));
}
代码稍微复杂一些。要写入数据库,我们需要一个事务,因此我们使用 Panache.withTransaction
来(异步地)获取一个事务,然后调用 persist
方法。persist
方法返回一个 Uni
,该 Uni
发出将水果插入数据库的结果。一旦插入完成(扮演延续的角色),我们就会创建一个 201 CREATED
HTTP 响应。
如果您的机器上有 curl,您可以尝试使用以下命令调用该端点
> curl --header "Content-Type: application/json" \
--request POST \
--data '{"name":"peach"}' \
https://:8080/fruits
按照相同的思路,您可以实现其他 CRUD 方法。
测试和运行
测试响应式应用程序与测试非响应式应用程序类似:使用 HTTP 端点并验证 HTTP 响应。应用程序是响应式的这一事实不会改变任何内容。
在 FruitsEndpointTest.java 中,您可以看到如何实现水果应用程序的测试。
打包和运行应用程序也没有改变。
您可以像往常一样使用以下命令
quarkus build
./mvnw install
./gradlew build
或构建本机可执行文件
quarkus build --native
./mvnw install -Dnative
./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:///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:///fruits \
-Dquarkus.datasource.username=quarkus \
-Dquarkus.datasource.password=quarkus
传递给应用程序的参数在数据源指南中进行了描述。还有其他配置应用程序的方法 - 请查看 配置指南,以了解各种可能性(例如环境变量、.env 文件等)。