使用虚拟线程编写 CRUD 应用程序
上周,我们发布了一个视频,演示了如何在 Quarkus 中使用虚拟线程创建 CRUD 应用程序。这很简单,只需在 HTTP 资源(如果您使用 Spring 兼容层,则为控制器类)上添加 @RunOnVirtualThread
注解即可。
这篇配套文章解释了它在幕后的工作原理。
代码
该应用程序是 Todo Backend 的简单实现。本文的完整代码可在此处找到。
重要的是 TodoResource.java
package org.acme.crud;
import io.quarkus.logging.Log;
import io.quarkus.panache.common.Sort;
import io.smallrye.common.annotation.NonBlocking;
import io.smallrye.common.annotation.RunOnVirtualThread;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import java.util.List;
@Path("/api")
@RunOnVirtualThread
public class TodoResource {
/**
* Just print on which thread the method is invoked.
*/
private void log() {
Log.infof("Called on %s", Thread.currentThread());
}
@GET
public List<Todo> getAll() {
log();
return Todo.listAll(Sort.by("order"));
}
@GET
@Path("/{id}")
public Todo getOne(@PathParam("id") Long id) {
log();
Todo entity = Todo.findById(id);
if (entity == null) {
throw new WebApplicationException("Todo with id of " + id + " does not exist.",
Status.NOT_FOUND);
}
return entity;
}
@POST
@Transactional
public Response create(@Valid Todo item) {
log();
item.persist();
return Response.status(Status.CREATED).entity(item).build();
}
@PATCH
@Path("/{id}")
@Transactional
public Response update(@Valid Todo todo, @PathParam("id") Long id) {
log();
Todo entity = Todo.findById(id);
entity.id = id;
entity.completed = todo.completed;
entity.order = todo.order;
entity.title = todo.title;
entity.url = todo.url;
return Response.ok(entity).build();
}
@DELETE
@Transactional
public Response deleteCompleted() {
log();
Todo.deleteCompleted();
return Response.noContent().build();
}
@DELETE
@Transactional
@Path("/{id}")
public Response deleteOne(@PathParam("id") Long id) {
log();
Todo entity = Todo.findById(id);
if (entity == null) {
throw new WebApplicationException("Todo with id of " + id + " does not exist.",
Status.NOT_FOUND);
}
entity.delete();
return Response.noContent().build();
}
}
该应用程序使用
-
RESTEasy Reactive - Quarkus 推荐的 REST 堆栈。它支持虚拟线程。
-
Hibernate Validation - 用于验证用户创建的 Todos。
-
Hibernate ORM with Panache - 用于与数据库交互。
-
Agroal 连接池 - 用于管理和回收数据库连接。
-
Narayana 事务管理器 - 用于在事务中运行我们的代码。
-
PostgreSQL 驱动程序 - 因为我们使用 PostgreSQL 数据库
代码与 Quarkus 的常规 CRUD 服务实现类似,除了一行。我们在资源类(第 17 行)上添加了 @RunOnVirtualThread
注解。它指示 Quarkus 在虚拟线程而非常规平台线程上调用这些方法(在上一篇博文中了解更多关于差异的信息),包括 @Transactional
方法。
线程模型
正如我们在代码中所见,开发模型是同步的。与数据库的交互使用阻塞 API:您等待响应。这就是虚拟线程发挥魔力的地方。它不会阻塞平台线程,而只会阻塞虚拟线程。

因此,当另一个请求到来时,载体线程可以处理它。这极大地减少了在存在大量并发请求时所需的平台线程数量。结果,在同步和阻塞开发模型中使用的工作线程数量不再是瓶颈。
但是,这并不是说您使用虚拟线程就不会遇到并发限制。存在一个新的瓶颈:数据库连接池。当您与数据库交互时,您会从连接池(在本例中为 Agroal)请求连接。连接的数量不是无限的(默认是 20 个)。一旦所有连接都用完,您必须等到另一个进程完成并释放其连接。您仍然可以并发处理许多请求,但它们将等待数据库连接可用,从而降低响应时间。
结论
本文介绍了在 Quarkus 中使用虚拟线程实现 CRUD 应用程序。您现在可以使用命令式开发模型,而不会损害应用程序的并发性。这就像使用 RESTEasy Reactive 并添加一个注解一样简单:在您的资源上添加 @RunOnVirtualThread
。
我们对 Quarkus 和上游项目(如 Hibernate、Narayana、SmallRye Mutiny 等)进行了定制,使其与虚拟线程兼容。正如我们在其他文章中将看到的,大多数 Quarkus 扩展都已准备好与虚拟线程一起使用。
也就是说,虽然虚拟线程增加了并发性,但您很可能会遇到其他瓶颈,例如连接池中管理的数据库连接数。
在下一篇文章和视频中,我们将介绍如何测试我们的应用程序并检测固定。