虚拟线程不适用于 Spring Boot WebFlux 中的 POST 请求

问题描述 投票:0回答:1
spring-boot spring-webflux java-21 virtual-threads
1个回答
0
投票

每个类似

@RequestMapping
注释的方法都会出现该问题,该方法接受一个用
@RequestBody
注释的参数。在这种情况下,该方法不会在 WebFlux 的
ExecutorScheduler
/
TaskExecutor
托管线程 (
task-<N>
) 上执行,而是在 WebFlux 事件循环 (
reactor-http-nio-<N>
) 上执行。

因此,这个问题并不特定于虚拟线程,显然它出现在平台线程中,如果平台

TaskExecutor
,例如
ThreadPoolTaskExecutor
,配置为运行控制器的方法。相反,这个问题特定于 Spring-WebFlux 桥与 WebFlux 本身的交互,可能是
Mono
子类的复杂层次结构(GET 请求落在
Just
上,带有主体的请求 - 位于其他东西上)。这个桥,
WebFluxConfigurationSupport
,将所谓的阻塞执行器设置为
RequestMappingHandlerAdapter
,负责调用控制器的方法。

事实上,这样的配置是 Spring Web-Flux 集成的最新介绍。 Rossen Stoyanchev 在 2023 年 6 月 15 日的文章阻止 WebFlux 控制器方法的执行中写道:

我们可以通过阻止执行的选项来增强 WebFuxConfigurer,例如配置 AsyncTaskExecutor,例如新的 VirtualThreadTaskExecutor。

因此,如果我们使用 Spring Boot 2.5.0 版本(它带来了 WebFlux 3.4.6),那么我们可以看到,即使是 GET 请求也不是在执行器上执行,而是在 WebFlux 事件循环上执行。因此,这种思想对于 Spring WebFlux 来说相对较新,我们可能期望对其进行进一步改进,同时将这种情况报告为错误。

与此同时,作为临时解决方法,我可以提供简单且批发的(дубовoe)解决方案,该解决方案仍然允许在执行程序管理的线程上运行有问题的方法。

AOP 方面拦截对这些方法的所有调用,并在其管理的线程上调用它们。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class ManagedThreadAspect {

    private static class Worker implements Runnable {
    
        private final ProceedingJoinPoint pjp;
        private volatile Object result;
        private volatile Throwable exception;

        public Worker(final ProceedingJoinPoint pjp) {
            this.pjp = pjp;
        }
    
        @Override
        public void run() {
            try {
                result = pjp.proceed();
            } catch (Throwable e) {
                exception = e;
            }
        }

        public Object getResult() throws Throwable {
            if (exception != null)
                throw exception;
            return result;
        }

    }

    @Around("execution(* *(.., @org.springframework.web.bind.annotation.RequestBody (*), ..))")
    public Object invokeOnManagedThread(final ProceedingJoinPoint pjp) throws Throwable {
        final Worker worker = new Worker(pjp); // this is nothing more than a result holder
        Thread.ofVirtual().start( () -> {
            worker.run();
        }).join();
        return worker.getResult();
    }
    
}

或者,如果您想重用库存执行器,并因此使用

spring.threads.virtual.enabled=true
设置,则注入执行器并调用它:

@Autowired
private AsyncTaskExecutor taskExecutor;
...
final Worker worker = new Worker(pjp);
taskExecutor.submit(worker).get();              
return worker.getResult();

效果与 WebFlux 管理的执行器调用此方法大致相同:

@RequestBody
方法将在虚拟(或者通常是 Aspect 管理)线程上调用。

Spring AOP应该适当配置,例如其中一个

@Configuration
类应使用
@EnableAspectJAutoProxy(proxyTargetClass=true)
进行注释。

如果上述方法对你有用,我可以在 github 上发布一个示例,或者向你做一个 PR。

然而,从概念上讲,在 WebFlux 中使用虚拟线程会引发一些问题。 WebFlux 的关键思想是在其事件循环中“非阻塞”处理请求,而虚拟线程本质上带来的是“阻塞”。因此,WebFlux 和虚拟线程即使不是相互排斥的,也被许多人视为并发方法。例如,Baldun 博客的作者,其标题为“Reactor WebFlux vs Virtual Threads”,得出的结论是: 我们比较了两种不同的并发和异步处理方法。

当然,这并不意味着虚拟线程在 WebFlux 中应该被视为无稽之谈,只有运行现实世界的 Web 应用程序才能揭示这种架构的优缺点。

© www.soinside.com 2019 - 2024. All rights reserved.