如何在 Quarkus 中异步执行后台任务而不阻塞 REST 响应

夜墨大大_3934

夜墨大大_3934

2026-07-20

185人浏览

原创

如何在 Quarkus 中异步执行后台任务而不阻塞 REST 响应

本文详解如何在 Quarkus 应用中真正实现非阻塞后台任务执行——通过正确使用 Mutiny Uni 订阅机制、@Blocking 注解及专用 WorkerExecutor,确保耗时操作(如数据库更新、打印标记)在后台运行,REST 接口即时返回响应。

本文详解如何在 quarkus 应用中真正实现非阻塞后台任务执行——通过正确使用 mutiny `uni` 订阅机制、`@blocking` 注解及专用 `workerexecutor`,确保耗时操作(如数据库更新、打印标记)在后台运行,rest 接口即时返回响应。

在 Quarkus 中实现“发完即忘”(fire-and-forget)式后台任务看似简单,但极易因 Reactive 编程模型的惰性求值特性而失败——未订阅的 Uni 永远不会执行。你遇到的 Uni.createFrom().voidItem().invoke(...).emitOn(executor) 无日志输出,正是典型表现:链式调用构建了异步流程,却缺少最终的 .subscribe() 触发器。

✅ 正确做法:必须显式订阅 + 合理线程调度

以下是最简、可靠且符合 Quarkus 最佳实践的解决方案:

1. 使用 @Blocking + 显式订阅(推荐用于事件总线场景)

@ApplicationScoped
public class TicketPrintHandler {

    private static final Logger LOG = Logger.getLogger(TicketPrintHandler.class);

    @Inject
    DaoBooking daoBooking;

    @Inject
    ManagedExecutor executor;

    @ConsumeEvent("greeting")
    @Blocking // 关键:将此方法调度到 Worker 线程池,避免阻塞 Vert.x Event Loop
    public void markSeatsAsPrinted(String bookingId) {
        LOG.infof("Received event for booking %s", bookingId);

        // 构建 Uni 并立即订阅(触发执行)
        Uni.createFrom().voidItem()
                .invoke(() -> {
                    LOG.infof("Starting seat marking for booking %s...", bookingId);
                    try {
                        daoBooking.markSeatsAsPrinted(bookingId); // 可能含 JDBC/IO 阻塞操作
                    } catch (FileMakerException e) {
                        LOG.error("Failed to mark seats", e);
                        throw new RuntimeException(e);
                    }
                    LOG.infof("Seat marking completed for booking %s", bookingId);
                })
                .emitOn(executor) // 显式指定在 ManagedExecutor 上执行(可选,@Blocking 已保证线程安全)
                .subscribe()
                .with(
                    ignored -> LOG.infof("Background task for booking %s finished successfully", bookingId),
                    failure -> LOG.error("Background task failed", failure)
                );
    }
}

⚠️ 注意事项:

夸克扫描王 - 转Office Alibaba-Quark-Transoffice
夸克扫描王 - 转Office Alibaba-Quark-Transoffice

由夸克扫描王提供的文件格式转换工具。当用户需要将图片、截图或扫描件转换为 Office 文档(Word/Excel)或 PDF 时,使用此技能。适用于包含复杂表格、合同或图文混排内容的图片或扫描件,可尽量还原原始版式并生成可编辑文档。即使用户未明确提到格式转换,只要用户的需求涉及将图片内容转换为可编辑文档(如 .docx、.xlsx 或 .pdf),也应触发此技能。请勿用于提取纯文本或识别文字内容、图像增强处理或从零创建文档

下载
  • @Blocking 是核心:它让 Quarkus 自动将该方法路由至专用 Worker 线程池,避免 I/O 阻塞影响 HTTP 请求处理。
  • .subscribe() 不可省略:它是 Reactive 流的“启动开关”,无订阅则无执行。
  • emitOn(executor) 在 @Blocking 下非必需,但显式声明更清晰;若需自定义线程池(如隔离 DB 操作),可配合 @Named("db-worker") 使用。

2. 更优方案:绕过 EventBus,直接使用 WorkerExecutor(生产推荐)

事件总线适用于松耦合通信,但对单一服务内“本地后台任务”,直接使用 Vert.x 的 WorkerExecutor 更轻量、可控且无额外序列化开销:

@Singleton
@Startup
public class BackgroundTaskManager {

    private static final Logger LOG = Logger.getLogger(BackgroundTaskManager.class);

    private final WorkerExecutor workerExecutor;

    public BackgroundTaskManager(Vertx vertx) {
        // 创建专用工作线程池,名称可监控、可配置
        this.workerExecutor = vertx.createSharedWorkerExecutor(
            "seat-marking-worker",
            4, // 初始线程数
            60_000L // 最大任务执行时间(ms),超时自动中断
        );
    }

    public void tearDown(@Observes ShutdownEvent ev) {
        workerExecutor.close(); // 容器关闭时优雅释放
    }

    public void markSeatsAsPrinted(String bookingId) {
        LOG.infof("Scheduling seat marking for booking %s", bookingId);

        // executeBlocking 确保阻塞操作在 Worker 线程执行
        workerExecutor.executeBlocking(promise -> {
            try {
                LOG.infof("Executing seat marking for booking %s...", bookingId);
                daoBooking.markSeatsAsPrinted(bookingId);
                LOG.infof("Seat marking completed for booking %s", bookingId);
                promise.complete();
            } catch (Exception e) {
                LOG.error("Seat marking failed for booking " + bookingId, e);
                promise.fail(e);
            }
        }).onFailure().recoverWithItem(() -> null) // 忽略后台失败(按业务需求调整)
         .subscribe().with(
             ignored -> LOG.debugf("Background seat marking for %s acknowledged", bookingId),
             err -> LOG.warn("Background task dispatch failed", err)
         );
    }
}

对应资源类调用:

@Path("/booking")
@ApplicationScoped
public class BookingResource {

    @Inject
    BackgroundTaskManager taskManager;

    @POST
    @Path("/{bookingId}/print-tickets/")
    @Produces(MediaType.APPLICATION_JSON)
    public PdfTicket printTickets(@PathParam("bookingId") String bookingId) throws Exception {
        var optBooking = daoBooking.getBookingDetailsById(bookingId);
        // ... 其他同步逻辑(PDF 生成等)

        PdfTicket pdfTicket = myconverter(optBooking, eventOpt);

        // ✅ 真正非阻塞:调用后立即返回,不等待后台完成
        if (optBooking.hasFixedSeatingTickets()) {
            taskManager.markSeatsAsPrinted(bookingId); // 返回 void,无等待
        }

        return pdfTicket; // 响应毫秒级返回
    }
}

? 关键总结

问题 正确解法 原因
Uni 不执行 必须调用 .subscribe() 或 .subscribe().with(...) Mutiny 是懒加载流,无订阅=无执行
REST 被阻塞 方法加 @Blocking 或使用 executeBlocking() 防止阻塞 Vert.x Event Loop,保障高并发吞吐
线程资源不可控 使用 WorkerExecutor 替代默认 ManagedExecutor 可命名、可调优(线程数/超时)、可监控、职责分离
过度依赖 EventBus 优先选择直接 WorkerExecutor 调用 减少序列化、消息路由开销,逻辑更内聚

遵循以上模式,你的 REST 接口将严格保持低延迟响应,后台任务在独立线程中稳定执行,既满足业务需求,又符合 Quarkus 的 Reactive 和云原生设计哲学。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

8897

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

6102

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

5492

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

984

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

808

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1156

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2349

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19691

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1075

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
尚学堂java开发聊天室视频教程
尚学堂java开发聊天室视频教程

共16课时 | 4.6万人学习

微信小程序开发实战视频教程
微信小程序开发实战视频教程

共8课时 | 4.6万人学习

阿里巴巴Java开发手册
阿里巴巴Java开发手册

共20课时 | 28.9万人学习