繁体   English   中英

如何将 Project Reactor 的调度程序与基于 Executor 的库一起使用?

[英]How to use Project Reactor's Scheduler with Executor based libraries?

Project Reactor 通过定义Scheduler提供了一种很好的方法来定义代码运行的线程池。 它还为使用CompletableFuture的库提供了一个桥梁,虽然Mono.fromFuture(..)

AWS 的DyanmoDB 异步客户端执行CompletableFuture ,它从java.util.concurrent.Executor上的 API 调用返回。 默认情况下,它会创建一个由它也创建的线程池支持的Executor 结果是,即使是具有像Mono.fromFuture(..).subscribeOn(Schedulers.boundedElastic())这样定义的Scheduler流,也会在库创建的池中的线程上执行,而不是在Schedulers.boundedElastic()的线程上执行。 所以我们看到线程名称像sdk-async-response-0-2 ,而不是像boundedElastic-1这样的名称。

幸运的是,该库允许我们提供我们自己的Executor如下所示,所以我的问题是:

您如何构建一个Executor ,该Executor在运行时使用在该部分流定义Scheduler的线程?

用例

我们有一个存储库类,它有一个findById方法,我们需要调用者能够控制在哪个Scheduler上运行,因为它在这些截然不同的上下文中使用:

  1. Schedulers.boundedElastic()调度Schedulers.boundedElastic()上运行的 API 响应。
  2. 处理按顺序执行的 Kafka 消息,在每个分区的线程上,来自定义的调度程序,如Reactor Kafka docs 所示

尝试

我们已经尝试使用Schedulers.immediate()Runnable::run定义一个Executor ,如下所示,但两者都导致在 Netty 事件循环线程上执行(示例名称: aws-java-sdk-NettyEventLoop-0-2 ) ,不是来自定义的Scheduler的线程。

DynamoDbAsyncClient.builder()
    .asyncConfiguration(builder -> builder.advancedOption(
        SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR,
        runnable -> Schedulers.immediate().schedule(runnable)
    ))
    .build();
DynamoDbAsyncClient.builder()
    .asyncConfiguration(builder -> builder.advancedOption(
        SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR,
        Runnable::run
    ))
    .build();

第 1 部分。观察与订阅

调查这个问题,我看到在特定线程上执行后需要观察元素。 准确地说,在这种情况下观察意味着*能够在某个特定线程上处理流中的值。 在 RxJava 中,我们有一个正确的操作符,就像这样,但在 Project Reactor 中,我们将相同的操作称为publishOn

因此, * 如果您想Schedulers.boundedElastic()处理数据,那么您应该使用以下构造

Mono.fromFuture(..)
    .publishOn(Schedulers.boundedElastic())

但是等等, .subscribeOn也有效???

阅读之前的构造,您可能会开始担心,因为您 100% 确定

Mono.fromRunnable(..)
    .subscribeOn(Schedulers.boundedElastic())

在线程boundedElastic-1上发送onNext ,那么相同的fromFuture有什么问题。

这里有一个技巧:

永远不要将subscribeOnFutures / CompletableFuture或任何可以在下面使用自己的异步机制的东西一起使用

如果我们查看subscribeOn背后发生的事情,您会发现如下内容:

//  Simplified version of SubscribeOn operator
@Override
public void subscribe(CoreSubscriber<? super T> actual) {
    Scheduler scheduler;
    Publisher<T> parent;
    scheduler.schedule(() -> parent.subscribe(actual));
}

这基本上意味着将在单独的线程上调用父级的subscribe方法。

这种技术适用于fromRunnablefromSupplierfromCallable因为它们的逻辑发生在subscribe方法中:

@Override
public void subscribe(CoreSubscriber<? super T> actual) {
    Operators.MonoSubscriber<T, T>
    sds = new Operators.MonoSubscriber<>(actual);

    actual.onSubscribe(sds);
    // skiped some parts 
    T t = supplier.get();
    if (t == null) {
        sds.onComplete();
    }
    else {
        sds.complete(t);
    }
}

这意味着它几乎等于

scheduler.schedule(() -> {
    T t = supplier.get();
    if (t == null) {
        sds.onComplete();
    }
    else {
        sds.complete(t);
    }
})

相比之下, fromFuture工作要复杂得多。 一个简短的测验。

我们可以在哪个线程上观察到一个值? (假设在线程 Main 上执行,并且任务在 ForkJoinPool 上执行)

var future = CompletableFuture
.supplyAsync(() -> {
  return value;
})
... // some code here, does not metter just code

future.thenAccept(value -> {
  System.out.println(Thread.currentThread())
});

和正确答案....🥁🥁🥁🥁🥁🥁

它可能是线程主
或者它可能是来自 ForkJoinPool 的线程
...
因为它很活泼......而且在这一点上,我们消费了价值,价值可能已经交付,所以我们只是在读取器线程(线程 Main)上读取volatile字段,否则,线程 Main 将设置一个acceptor因此稍后将在ForkJoinPool线程上调用接受器。

是的,这就是为什么当您将fromFuturesubscribeOn一起使用时,不能保证subscribeOn线程会观察给定CompletableFuture的值。

这就是为什么publishOn是确保值处理发生在所需线程上的唯一方法。

好吧,我应该一直使用publishOn ???

是和否。 这取决于。

如果您使用Mono - 在 99% 的情况下,如果您想确保您的数据处理发生在特定线程上,您可以使用publishOn - 始终使用publishOn

不要担心潜在的开销,即使您不小心使用了 Project Reactor,它也会照顾您。 Project Reactor 有几个优化,可以在运行时用subscribeOn替换你的publishOn (如果它是安全的而不破坏行为),那么你会得到最好的。

Part 2. Scheduelr的兔子洞

永远不要使用Schedulers.immediate()

它几乎是无操作调度程序,基本上可以

Schedulers.immediate().scheduler(runnable) {
   runnable.run()
}

是的,它对反应堆用户没有任何用处,我们仅将其用于内部需求。

好的,那么我如何使用调度程序在命令式世界中将其用作执行程序

有两种选择:

快速路径:分步指南

1.a) 创建您的有界Executor (例如Executors.fixed...
1.b) 如果您想获得周期性任务和延迟任务的力量,请创建您的有界ScheduledExecutorService
2) 使用Schedulers.fromExecutorXXX API 从您的执行Schedulers.fromExecutorXXX创建Scheduler Schedulers.fromExecutorXXX
3)在命令式世界中使用你的有界Executor ,使用你的Scheduler ,它是反应世界的有界Executor的包装器

长路

即将推出...

第 3 部分。如何序列化执行。

即将推出

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM