Java SpringWebFlux在反应堆栈中使用阻塞HttpClient

Java SpringWebFlux在反应堆栈中使用阻塞HttpClient,java,spring,spring-boot,reactive-programming,spring-webflux,Java,Spring,Spring Boot,Reactive Programming,Spring Webflux,我目前正在进行一个构建微服务的项目,并试图从更传统的Spring BootRestClient转移到使用Netty和WebClient作为HTTP客户端的反应式堆栈,以便连接到后端系统 这对于使用RESTAPI的后端来说是很好的,但是我在将WebClient实现到连接到SOAP后端和Oracle数据库的服务时仍然遇到一些困难,因为Oracle数据库仍然使用传统的JDBC 我设法在网上找到了一些关于JDBC调用的解决办法,这些调用使用并行调度器来发布阻塞JDBC调用的结果: //the metho

我目前正在进行一个构建微服务的项目,并试图从更传统的Spring Boot
RestClient
转移到使用Netty和
WebClient
作为HTTP客户端的反应式堆栈,以便连接到后端系统

这对于使用RESTAPI的后端来说是很好的,但是我在将
WebClient
实现到连接到SOAP后端和Oracle数据库的服务时仍然遇到一些困难,因为Oracle数据库仍然使用传统的JDBC

我设法在网上找到了一些关于JDBC调用的解决办法,这些调用使用并行调度器来发布阻塞JDBC调用的结果:

//the method that is called by @Service
@Override
public Mono<TransactionManagerModel> checkTransaction(String transactionId, String channel, String msisdn) {
    return asyncCallable(() -> checkTransactionDB(transactionId, channel, msisdn))
            .onErrorResume(error -> Mono.error(error));
}

...

//the actual JDBC call
private TransactionManagerModel checkTransactionDB(String transactionId, String channel, String msisdn) {
...
    List<TransactionManagerModel> result = 
                    jdbcTemplate.query(CHECK_TRANSACTION, paramMap, new BeanPropertyRowMapper<>(TransactionManagerModel.class));
...
}

//Generic async callable
private <T> Mono<T> asyncCallable(Callable<T> callable) {
    return Mono.fromCallable(callable).subscribeOn(Schedulers.parallel()).publishOn(transactionManagerJdbcScheduler);
}
我的问题是:这个SOAP调用的实现是否足够“反应性”,以至于我不必担心某些调用在微服务的某些部分被阻塞?我已经实现了反应堆栈-显式调用
block()
将引发异常,因为如果使用Netty,则不允许这样做


或者我应该在SOAP调用中使用并行
调度程序吗?

经过一些讨论后,我将写一个答案

反应堆文件表明。这基本上是为了保持reactor的非阻塞部分继续运行,如果阻塞中出现了什么,reactor将退回到传统的servlet行为,这意味着为每个请求分配一个线程

反应堆对其类型等有很好的文档记录

但简而言之:

认购 当有人订阅时,reactor将进入一个称为
组装阶段
,这意味着它将基本上从订阅点开始向后向上游调用操作符,直到找到数据生产者(例如数据库或其他服务等)。如果在这个阶段的某个地方发现了一个
onSubscribe
-操作符,它将把整个链放在自己定义的
调度器上。因此,要知道的一件好事是,
onSubscribe
的位置其实并不重要,只要在
组装阶段找到它,整个链就会受到影响

示例用法可以是:

我们有对数据库的阻塞调用、使用阻塞rest客户端的慢速调用、在阻塞庄园中从系统读取文件等

发布 如果在
组装阶段链中的某个位置有
onPublish
,链将知道它放置的位置,链将在该特定点从默认计划程序切换到指定的计划程序。因此,
onPublish
位置确实很重要。因为它将在放置位置切换。这个操作符更能控制您希望在代码中的特定点将某些内容放置在特定的调度程序上

使用示例可以是:

如果您在某个特定点执行一些严重阻塞的cpu计算,则可以切换到调度程序。Parallel()
,这将确保所有计算都放在单独的内核上,这样会产生大量cpu工作,完成后可以切换回默认调度程序

上例
如果soap调用被阻塞,则应该将其放置在自己的
调度程序上,我认为
onSubscribe
使用
调度程序就足够了。elasticBound()
可以获得传统的servlet行为。如果您觉得害怕在同一个调度程序上执行每个阻塞调用,您可以在
asyncCallable
函数中传入
调度程序
,并拆分调用以使用不同的
调度程序

执行反应式关系数据库调用的一个选项是R2DBC-有一个等效的spring数据库。@MichaelMcFadyen我已经看过R2DBC,但不幸的是,对Oracle DB驱动程序的支持……缺乏。Oracle反应式驱动程序需要JDK11,而我的项目在8时仍然停滞不前。但对于肥皂,我仍然不是真正的好观点。我不知道oracle驱动程序还不支持,直到。所有阻塞调用都应该放在它们自己的调度程序中,这样它们就不会阻塞任何常规的调度线程。您的soap请求可能应该像处理jdbc调用一样完成。我唯一的看法是,
Schedulers.parallel()
将在多个内核上创建工作线程,这可能是不需要的,因为有一个设置时间,并且通常只有在cpu密集型工作时才需要使用多个内核。第二件事是使用
publishOn
。因此,您现在声明的是,当有人订阅时,他们将自动在随机核心上分配一个线程,然后,当我们到达
publishOn
语句时,当前线程将从指定核心上的指定线程切换到
transactionManagerJdbcScheduler
中的线程(我不知道这是什么类型的
调度器
。我可能从一个
调度器开始
放置在订阅上的
onSubscribe
上,因为此计划程序将根据需要进行放大和缩小,并在60秒后删除未使用的线程。它有一个cpu内核x 10线程的上限。谢谢!在讨论之后,我还可以根据JDBC调用进行更多调整。很高兴能够了解更多关于反应式的信息。
//The method that is being 'reactive'
public Mono<OfferRs> addOffer(String transactionId, String channel, String serviceId, OfferRq request) {
...
    OfferRs result = adapter.addOffer(transactionId, channel, generateRequest(request));
...
}

//The SOAP adapter that uses blocking HTTP Client
public OfferRs addOffer(String transactionId, String channel, JAXBElement<OfferRq> request) {
...
    response = (OfferRs) getWebServiceTemplate().marshalSendAndReceive(url, request, webServiceMessage -> {
            try {
                SoapHeader soapHeader = ((SoapMessage) webServiceMessage).getSoapHeader();
                    
                ObjectFactory headerFactory = new ObjectFactory();
                AuthenticationHeader authHeader = headerFactory.createAuthenticationHeader();
                authHeader.setUserName(username);
                authHeader.setPassWord(password);
                    
                JAXBContext headerContext = JAXBContext.newInstance(AuthenticationHeader.class);
                Marshaller marshaller = headerContext.createMarshaller();
                marshaller.marshal(authHeader, soapHeader.getResult());
            } catch (Exception ex) {
                log.error("Failed to marshall SOAP Header!", ex);
            }
        });
        return response;
...
}