据我所知,其余的下游需要在线程池中的线程上进行处理(我将其设置为1024)
这是我的代码。
Flux<String> ips =
Flux.fromIterable(items).map(Item::getIp);
ips
.publishOn(Schedulers.fromExecutor(Executors.newFixedThreadPool(1024)))
.map(ip -> {
try {
Request request = new Request.Builder().url("https://" + ip + ":443").build();
Response response = okHttpClient.newCall(request).execute();
return response.code();
} catch (Exception e) {
}
return -1;
})
.subscribe(System.out::println);
由于某些原因,此代码与以下代码相比非常慢:
appRules
.stream()
.parallel()
.map(Item::getIp)
.forEach(ip -> {
try {
Request request = new Request.Builder().url("https://" + ip + ":443").build();
Response response = okHttpClient.newCall(request).execute();
System.out.println(response.code());
} catch (Exception e) {
}
System.out.println(-1);
});
为什么?当您受IO限制时,同时处理项目流的正确方法是什么? (而不是CPU)
答案 0 :(得分:2)
执行速度较慢的原因是,默认情况下Reactor管道执行是单线程的。因此,当您使用Flux.publishOn
运算符时,您只是说要在给定线程池中的线程上执行这部分管道,但不会同时在单独的线程上执行每个项目。
实现并发的一种选择是使用parallel Flux,它创建了所谓的Rails,数据可以在其中并行流动,但主要用于CPU绑定操作。
更好的选择是将阻塞代码包装在Mono中,然后将其委托给专用的线程池,类似于您所做的事情,只是这次每个任务都将获得自己的线程:
private static void reactorProcess()
{
ExecutorService executor = Executors.newFixedThreadPool(1024);
Flux.range(1, 1024)
.flatMap(a -> Mono.fromRunnable(() -> simulateHttpCall())
.subscribeOn(Schedulers.fromExecutor(executor)))
.blockLast();
executor.shutdown();
}
private static void simulateHttpCall()
{
try
{
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ": " + ZonedDateTime.now());
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
我还要指出,Java并行流不是这种处理的可行替代方案。默认情况下,它使用ForkJoinPool,它也用于CPU绑定操作,并且仅使用与计算机中具有的CPU内核数量一样多的线程。
除此之外,如果您想利用反应式编程的全部功能,则应考虑使用支持无阻塞IO的HTTP客户端,例如Spring的WebClient。通过使用非阻塞HTTP客户端,您不再需要担心定义线程池,因为不会阻塞任何线程,并且固定数量的线程将能够处理数千个并发请求。