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 方面都有成本。
另一方面,反应式模型依赖于非阻塞 I/O 和不同的执行模型。非阻塞 I/O 提供了一种处理并发 I/O 的有效方法。称为 I/O 线程的线程量极小,可以处理许多并发 I/O。使用这种模型,请求处理不会委托给工作线程,而是直接使用这些 I/O 线程。由于不需要创建工作线程来处理请求,因此节省了内存和 CPU。它还提高了并发性,因为它消除了线程数量的限制。最后,它还缩短了响应时间,因为它减少了线程切换的数量。
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 应用程序。
尽管我们建议您遵循分步式说明,但您可以在 [role="bare"]https://github.com/quarkusio/quarkus-quickstarts/tree/main/hibernate-reactive-panache-quickstart中找到最终解决方案。
首先,转到 code.quarkus.io并选择以下扩展:
-
Quarkus REST Jackson
-
Hibernate Reactive with Panache
-
Reactive PostgreSQL client
该最后一个扩展是适用于 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” 中,你可以查看如何实现水果应用程序的测试。
应用程序的打包和运行也不会改变。
你可以像往常一样使用以下命令:
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://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 文件等)。