gpt4 book ai didi

java - Reactor - 了解 .flatMap() 中的线程池

转载 作者:行者123 更新时间:2023-11-30 05:33:31 27 4
gpt4 key购买 nike

我尝试了解响应式编程的真正工作原理。为此,我准备了简单的演示:Spring Framework 中的响应式(Reactive) WebClient 将请求发送到简单的 REST API,并且该客户端在每个操作中打印线程的名称。

其余API:

@RestController
@SpringBootApplication
public class RestApiApplication {

public static void main(String[] args) {
SpringApplication.run(RestApiApplication.class, args);
}

@PostMapping("/resource")
public void consumeResource(@RequestBody Resource resource) {
System.out.println(String.format("consumed resource: %s", resource.toString()));
}
}

@Data
@AllArgsConstructor
class Resource {
private final Long id;
private final String name;
}

还有最重要的 - 响应式 Web 客户端:

@SpringBootApplication
public class ReactorWebclientApplication {

public static void main(String[] args) {
SpringApplication.run(ReactorWebclientApplication.class, args);
}

private final TcpClient tcpClient = TcpClient.create();

private final WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.baseUrl("http://localhost:8080")
.build();

@PostConstruct
void doRequests() {
var longs = LongStream.range(1L, 10_000L)
.boxed()
.toArray(Long[]::new);

var longsStream = Stream.of(longs);

Flux.fromStream(longsStream)
.map(l -> {
System.out.println(String.format("------- map [%s] --------", Thread.currentThread().getName()));
return new Resource(l, String.format("name %s", l));
})
.filter(res -> {
System.out.println(String.format("------- filter [%s] --------", Thread.currentThread().getName()));
return !res.getId().equals(11_000L);
})
.flatMap(res -> {
System.out.println(String.format("------- flatmap [%s] --------", Thread.currentThread().getName()));
return webClient.post()
.uri("/resource")
.syncBody(res)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.retrieve()
.bodyToMono(Resource.class)
.doOnSuccess(ignore -> System.out.println(String.format("------- onsuccess [%s] --------", Thread.currentThread().getName())))
.doOnError(ignore -> System.out.println(String.format("------- onerror [%s] --------", Thread.currentThread().getName())));
})
.blockLast();
}

}

@JsonIgnoreProperties(ignoreUnknown = true)
class Resource {
private final Long id;
private final String name;

@JsonCreator
Resource(@JsonProperty("id") Long id, @JsonProperty("name") String name) {
this.id = id;
this.name = name;
}

Long getId() {
return id;
}

String getName() {
return name;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Resource{");
sb.append("id=").append(id);
sb.append(", name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}

问题是行为与我的预测不同。

我预计 .map().filter().flatMap() 的每次调用都将在 上执行>main 线程,每次调用 .doOnSuccess().doOnError 都将在 nio 线程池的线程上执行。所以我期望日志看起来像:

------- map [main] --------
------- filter [main] --------
------- flatmap [main] --------
(and so on...)
------- onsuccess [reactor-http-nio-2] --------
(and so on...)

但是我得到的日志是:

------- map [main] --------
------- filter [main] --------
------- flatmap [main] --------
------- map [main] --------
------- filter [main] --------
------- flatmap [main] --------
------- onsuccess [reactor-http-nio-2] --------
------- onsuccess [reactor-http-nio-6] --------
------- onsuccess [reactor-http-nio-4] --------
------- onsuccess [reactor-http-nio-8] --------
------- map [reactor-http-nio-2] --------
------- filter [reactor-http-nio-2] --------
------- flatmap [reactor-http-nio-2] --------
------- map [reactor-http-nio-2] --------

并且.map().filter().flatMap()中的每个下一个日志都是在reactor-的线程上完成的http-nio。

下一个令人费解的事实是主线程和reactor-http-nio上执行的操作之间的比例总是不同的。有时,所有操作 .map().filter().flatMap() 都在主线程上执行。

最佳答案

Reactor 与 RxJava 一样,可以被认为是与并发无关的。也就是说,它不强制执行并发模型。相反,它让你(开发人员)来指挥。但是,这并不妨碍该库帮助您处理并发性。

获得FluxMono并不一定意味着它在专用线程中运行。相反,大多数运算符继续在前一个运算符执行的线程中工作。除非指定,否则最顶层的运算符(源)本身在进行 subscribe() 调用的线程上运行。

Project Reactor相关文档可以找到here .

从您的代码中,得到以下代码片段:

webClient.post()
.uri("/resource")
.syncBody(res)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.retrieve()
.bodyToMono(Resource.class)

导致线程从main切换到netty的工作池。之后,以下所有操作均由netty工作线程执行。

如果您想控制此行为,您应该在代码中添加 publishOn(...) 语句,例如:

webClient.post()
.uri("/resource")
.syncBody(res)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.retrieve()
.bodyToMono(Resource.class)
.publishOn(Schedulers.elastic())

这样,接下来的任何操作都将由弹性调度程序线程池执行。

另一个例子是使用专用调度程序来处理 HTTP 请求执行后的繁重任务。

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;

import com.github.tomakehurst.wiremock.WireMockServer;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import ru.lanwen.wiremock.ext.WiremockResolver;
import ru.lanwen.wiremock.ext.WiremockResolver.Wiremock;
import ru.lanwen.wiremock.ext.WiremockUriResolver;
import ru.lanwen.wiremock.ext.WiremockUriResolver.WiremockUri;

@ExtendWith({
WiremockResolver.class,
WiremockUriResolver.class
})
public class ReactiveThreadsControlTest {

private static int concurrency = 1;

private final WebClient webClient = WebClient.create();

@Test
public void slowServerResponsesTest(@Wiremock WireMockServer server, @WiremockUri String uri) {

String requestUri = "/slow-response";

server.stubFor(get(urlEqualTo(requestUri))
.willReturn(aResponse().withStatus(200)
.withFixedDelay((int) TimeUnit.SECONDS.toMillis(2)))
);

Flux
.generate(() -> Integer.valueOf(1), (i, sink) -> {
System.out.println(String.format("[%s] Emitting next value: %d", Thread.currentThread().getName(), i));
sink.next(i);
return i + 1;
})
.subscribeOn(Schedulers.single())
.flatMap(i ->
executeGet(uri + requestUri)
.publishOn(Schedulers.elastic())
.map(response -> {
heavyTask();
return true;
})
, concurrency)
.subscribe();

blockForever();
}

private void blockForever() {
Object monitor = new Object();

synchronized (monitor) {
try {
monitor.wait();
} catch (InterruptedException ex) {
}
}
}


private Mono<ClientResponse> executeGet(String path) {
System.out.println(String.format("[%s] About to execute an HTTP GET request: %s", Thread.currentThread().getName(), path));
return webClient
.get()
.uri(path)
.exchange();
}

private void heavyTask() {
try {
System.out.println(String.format("[%s] About to execute a heavy task", Thread.currentThread().getName()));
Thread.sleep(TimeUnit.SECONDS.toMillis(20));
} catch (InterruptedException ex) {
}
}
}

关于java - Reactor - 了解 .flatMap() 中的线程池,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57079395/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com