Use virtual threads in REST applications
在本指南中,我们将介绍如何在 REST 应用程序中使用虚拟线程。由于虚拟线程与 I/O 息息相关,因此我们还将使用 REST 客户端。
Prerequisites
如要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
安装了 JDK 17+,已正确配置
JAVA_HOME
-
Apache Maven ${proposed-maven-version}
-
如果你想使用 Quarkus CLI, 则可以选择使用
-
如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately
Create the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
--no-code
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 --gradle
或 --gradle-kotlin-dsl
选项。
有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId={create-app-group-id} \
-DprojectArtifactId={create-app-artifact-id} \
-DnoCode
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 -DbuildTool=gradle
或 -DbuildTool=gradle-kotlin-dsl
选项。
适用于 Windows 用户:
-
如果使用 cmd,(不要使用反斜杠
\
,并将所有内容放在同一行上) -
如果使用 Powershell,将
-D
参数用双引号引起来,例如"-DprojectArtifactId={create-app-artifact-id}"
此命令生成一个新项目,用于导入 Quarkus REST(以前称为 RESTEasy Reactive)、REST 客户端和 Jackson 扩展,此外特别添加了以下依赖项:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-jackson")
implementation("quarkus-rest-client-jackson")
您可能想知道我们为什么要使用 reactive 扩展。虚拟线程有局限性,只有在使用响应式扩展时,我们才能正确地集成它们。不必担心:您的代码将 100% 以同步/命令式的方式编写。 有关详细信息,请检查virtual thread reference guide。 |
Prepare the pom.xml
file
我们需要定制`pom.xml` 文件以使用虚拟线程。
1) 找到 <maven.compiler.release>17</maven.compiler.release>
所在的行,并用以下内容替换它:
<maven.compiler.release>21</maven.compiler.release>
2) 在 maven-surefire-plugin 和 maven-failsafe-plugin 配置中,添加以下 argLine
参数:
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
<argLine>-Djdk.tracePinnedThreads</argLine> <!-- Added line -->
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
<argLine>-Djdk.tracePinnedThreads</argLine> <!-- Added line -->
</configuration>
</execution>
</executions>
</plugin>
-Djdk.tracePinnedThreads
将在运行测试时检测固定的载体线程(请参阅 the virtual thread reference guide for details)。
如果您使用的是 Java 19 或 20,则在 argLine
和 maven 编译器插件的 parameters
中添加 --enable-preview
标志。请注意,我们强烈推荐 Java 21。
Create the weather client
本节与虚拟线程无关。因为我们需要进行一些 I/O 以演示虚拟线程的使用,我们需要一个执行 I/O 操作的客户端。此外,REST 客户端有利于虚拟线程:它不固定且能正确处理传播。
创建具有以下内容的 src/main/java/org/acme/WeatherService.java
类:
package org.acme;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.rest.client.reactive.ClientQueryParam;
import jakarta.ws.rs.GET;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestQuery;
@RegisterRestClient(baseUri = "https://api.open-meteo.com/v1/forecast")
public interface WeatherService {
@GET
@ClientQueryParam(name = "current_weather", value = "true")
WeatherResponse getWeather(@RestQuery double latitude, @RestQuery double longitude);
record WeatherResponse(@JsonProperty("current_weather") Weather weather) {
// represents the response
}
record Weather(double temperature, double windspeed) {
// represents the inner object
}
}
此类模拟与天气服务的 HTTP 交互。在专门的 guide 中了解有关 rest 客户端的更多信息。
Create the HTTP endpoint
现在,创建具有以下内容的 src/main/java/org/acme/TheBestPlaceToBeResource.java
类:
package org.acme;
import io.smallrye.common.annotation.RunOnVirtualThread;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Path("/")
public class TheBestPlaceToBeResource {
static final double VALENCE_LATITUDE = 44.9;
static final double VALENCE_LONGITUDE = 4.9;
static final double ATHENS_LATITUDE = 37.9;
static final double ATHENS_LONGITUDE = 23.7;
@RestClient WeatherService service;
@GET
@RunOnVirtualThread (1)
public String getTheBestPlaceToBe() {
var valence = service.getWeather(VALENCE_LATITUDE, VALENCE_LONGITUDE).weather().temperature();
var athens = service.getWeather(ATHENS_LATITUDE, ATHENS_LONGITUDE).weather().temperature();
// Advanced decision tree
if (valence > athens && valence <= 35) {
return "Valence! (" + Thread.currentThread() + ")";
} else if (athens > 35) {
return "Valence! (" + Thread.currentThread() + ")";
} else {
return "Athens (" + Thread.currentThread() + ")";
}
}
}
1 | 指示 Quarkus 在虚拟线程上调用此方法 |
Run the application in dev mode
确保您使用 OpenJDK 和支持虚拟线程的 JVM 版本,并使用 ./mvnw quarkus:dev
启动 dev mode:
> java --version
openjdk 21 2023-09-19 LTS 1
OpenJDK Runtime Environment Temurin-21+35 (build 21+35-LTS)
OpenJDK 64-Bit Server VM Temurin-21+35 (build 21+35-LTS, mixed mode)
> ./mvnw quarkus:dev 2
1 | 必须是 19+,我们建议使用 21+ |
2 | Launch the dev mode |
然后,在浏览器中,打开 [role="bare"][role="bare"]http://localhost:8080。您应获得类似以下内容的信息:
Valence! (VirtualThread[#144]/runnable@ForkJoinPool-1-worker-6)
如您所见,端点在虚拟线程上运行。
了解幕后发生了什么至关重要:
-
Quarkus 创建虚拟线程来调用您的端点(因为
@RunOnVirtualThread
注释)。 -
当代码调用 rest 客户端时,虚拟线程被阻塞,但载体线程没有被阻塞(这是虚拟线程 magic touch)。
-
出于休息客户端第一次调用的完成,虚拟线程重新安排并继续执行。
-
第二次休息客户端调用发生,虚拟线程再次被阻塞(但不是载波线程)。
-
最后,当休息客户端的第二次调用完成时,虚拟线程被重新安排并继续执行。
-
该方法返回结果。虚拟线程终止。
-
结果被 Quarkus 捕获并写入 HTTP 响应中
Verify pinning using tests
在`pom.xml,`中,我们向 surefire 和 failsafe 插件添加了`argLine`参数:
<argLine>-Djdk.tracePinnedThreads</argLine>
如果虚拟线程不能顺利_unmounted_(意味着它阻塞了载波线程),`-Djdk.tracePinnedThreads`倾倒堆栈轨迹。这就是我们称之为_pinning_的情况(更多信息请参阅the virtual thread reference guide)。
我们建议在测试中启用此标志。因此,您可以检查在使用虚拟线程时应用程序的行为是否正确。只需在运行测试后检查日志即可。如果您看到堆栈轨迹……最好检查一下有什么问题。如果您的代码(或依赖项之一)固定,最好使用常规工作线程。
使用以下内容创建`src/test/java/org/acme/TheBestPlaceToBeResourceTest.java`类:
package org.acme;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
@QuarkusTest
class TheBestPlaceToBeResourceTest {
@Test
void verify() {
RestAssured.get("/")
.then()
.statusCode(200);
}
}
这是一个简单的测试,但是它至少可以检测到我们的应用程序是否固定。使用以下任一命令运行测试:
-
`r`处于开发模式(使用持续测试)
-
./mvnw test
您将看到,它并未固定 - 没有堆栈轨迹。这是因为 REST 客户端是以对虚拟线程友好的方式实现的。
同样的方法也可以用于集成测试。
Conclusion
本指南演示了如何将虚拟线程与 Quarkus REST 和 REST 客户端配合使用。详细了解涉及虚拟线程支持的内容:
-
@RunOnVirtualThread in messaging applications(本指南涵盖了 Apache Kafka)
-
the virtual thread reference guide(包括本机编译和容器化)