在等待api调用完成时,并行调用N个阻塞api调用以充分利用CPU的最佳方法是什么?

时间:2019-05-08 11:01:55

标签: java concurrency microservices

我正在开发一种微服务,其操作流程如下:

enter image description here

请求执行一些任务。经过一些预处理后,我知道我需要执行一些任务,比方说10。现在,这些任务彼此独立,因此可以并行执行。每个任务都有一些处理步骤和一些外部API调用。在完成所有任务后,需要返回合并的结果。

这是一个请求,因此很明显,此微服务也可以并行获取许多这样的请求。

在这里,API调用是最耗时的操作,相对而言,其他工作执行的时间要短得多。因此,我想以一种可以并行执行许多任务的方式进行设计,因为对于API调用,大多数任务会被阻塞。

我看到的一个简单解决方案是使用ExecutorService使用线程池,但这似乎不是理想的解决方案,因为假设我创建了一个包含32个线程的线程池,并且得到60个任务。因此,即使这32个任务因api调用而被阻塞并且不占用太多CPU时间,一次也只能执行32个。

在不破坏单个任务的情况下是否可以实现?

4 个答案:

答案 0 :(得分:3)

最佳线程数取决于服务器具有的内核数以及I / O工作负载所花费的时间。请参见http://baddotrobot.com/blog/2013/06/01/optimum-number-of-threads/进行计算。

简而言之,它指出:threads = number of cores * (1 + wait time / service time)

时间必须来自您的观察和测量。 对于其余部分,您可以使用注释中提到的CompletableFuture,也可以使用Executors类:Executors.newFixedThreadPool(<num of threads>);

答案 1 :(得分:1)

一种解决方法是确保线程池始终至少包含n个处于可运行状态的线程(其中n通常等于CPU内核的数量)。这意味着您需要管理阻塞,并且只要线程开始阻塞,就将线程添加到池中,并在线程退出阻塞后再次将其删除。

Java的ForkJoinPool.ManagedBlocker是使用parallel streams时类似问题的解决方案的一部分。

Scala通过使用ExecutionContext时使用的futures来概括和简化这一方面。

答案 2 :(得分:0)

您将必须进行一些基准测试,以找出最适合您的设置的方法。您可能想研究使用ThreadPoolExecutor,它可以根据池中可用的线程数来增加和减少线程数。您可以在基准测试中调整一些参数,即corePoolSizemaximumPoolSize

答案 3 :(得分:0)

LEFT()具有一些控制参数(核心池大小(<32),最大池大小(60)),允许60个线程用于32个内核当28个活动线程被阻止时,它将很好地工作。

您描述的星座通常会使用任务队列,但是您要求的CPU使用率最高。尽管在微服务方面,其他方面(而不是核心)也发挥了作用。